mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-01-15 20:02:30 +00:00
Merge branch 'main' into feat/ldap-groups
This commit is contained in:
17
.env.example
17
.env.example
@@ -2,8 +2,6 @@
|
||||
|
||||
# The base URL where Tinyauth is accessible
|
||||
TINYAUTH_APPURL="https://auth.example.com"
|
||||
# Log level: trace, debug, info, warn, error
|
||||
TINYAUTH_LOGLEVEL="info"
|
||||
# Directory for static resources
|
||||
TINYAUTH_RESOURCESDIR="/data/resources"
|
||||
# Path to SQLite database file
|
||||
@@ -14,8 +12,21 @@ TINYAUTH_DISABLEANALYTICS="false"
|
||||
TINYAUTH_DISABLERESOURCES="false"
|
||||
# Disable UI warning messages
|
||||
TINYAUTH_DISABLEUIWARNINGS="false"
|
||||
|
||||
# Logging Configuration
|
||||
|
||||
# Log level: trace, debug, info, warn, error
|
||||
TINYAUTH_LOG_LEVEL="info"
|
||||
# Enable JSON formatted logs
|
||||
TINYAUTH_LOGJSON="false"
|
||||
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"
|
||||
|
||||
# Server Configuration
|
||||
|
||||
|
||||
@@ -3,13 +3,10 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/traefik/paerser/cli"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -43,7 +40,7 @@ func createUserCmd() *cli.Command {
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
@@ -77,7 +74,7 @@ func createUserCmd() *cli.Command {
|
||||
return errors.New("username and password cannot be empty")
|
||||
}
|
||||
|
||||
log.Info().Str("username", tCfg.Username).Msg("Creating user")
|
||||
tlog.App.Info().Str("username", tCfg.Username).Msg("Creating user")
|
||||
|
||||
passwd, err := bcrypt.GenerateFromPassword([]byte(tCfg.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
@@ -90,7 +87,7 @@ func createUserCmd() *cli.Command {
|
||||
passwdStr = strings.ReplaceAll(passwdStr, "$", "$$")
|
||||
}
|
||||
|
||||
log.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
|
||||
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s", tCfg.Username, passwdStr)).Msg("User created")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -5,15 +5,13 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/mdp/qrterminal/v3"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/paerser/cli"
|
||||
)
|
||||
|
||||
@@ -42,7 +40,7 @@ func generateTotpCmd() *cli.Command {
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
@@ -91,9 +89,9 @@ func generateTotpCmd() *cli.Command {
|
||||
|
||||
secret := key.Secret()
|
||||
|
||||
log.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
||||
tlog.App.Info().Str("secret", secret).Msg("Generated TOTP secret")
|
||||
|
||||
log.Info().Msg("Generated QR code")
|
||||
tlog.App.Info().Msg("Generated QR code")
|
||||
|
||||
config := qrterminal.Config{
|
||||
Level: qrterminal.L,
|
||||
@@ -112,7 +110,7 @@ func generateTotpCmd() *cli.Command {
|
||||
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
|
||||
}
|
||||
|
||||
log.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
||||
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -9,8 +9,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
"github.com/traefik/paerser/cli"
|
||||
)
|
||||
|
||||
@@ -27,7 +26,7 @@ func healthcheckCmd() *cli.Command {
|
||||
Resources: nil,
|
||||
AllowArg: true,
|
||||
Run: func(args []string) error {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
appUrl := os.Getenv("TINYAUTH_APPURL")
|
||||
|
||||
@@ -39,7 +38,7 @@ func healthcheckCmd() *cli.Command {
|
||||
return errors.New("TINYAUTH_APPURL is not set and no argument was provided")
|
||||
}
|
||||
|
||||
log.Info().Str("app_url", appUrl).Msg("Performing health check")
|
||||
tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check")
|
||||
|
||||
client := http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
@@ -77,7 +76,7 @@ func healthcheckCmd() *cli.Command {
|
||||
return fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
log.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
|
||||
tlog.App.Info().Interface("response", healthResp).Msg("Tinyauth is healthy")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -2,22 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/bootstrap"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/loaders"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/paerser/cli"
|
||||
)
|
||||
|
||||
func NewTinyauthCmdConfiguration() *config.Config {
|
||||
return &config.Config{
|
||||
LogLevel: "info",
|
||||
ResourcesDir: "./resources",
|
||||
DatabasePath: "./tinyauth.db",
|
||||
Server: config.ServerConfig{
|
||||
@@ -39,6 +35,24 @@ func NewTinyauthCmdConfiguration() *config.Config {
|
||||
Insecure: false,
|
||||
SearchFilter: "(uid=%s)",
|
||||
},
|
||||
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: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Experimental: config.ExperimentalConfig{
|
||||
ConfigFile: "",
|
||||
},
|
||||
@@ -102,25 +116,14 @@ func main() {
|
||||
}
|
||||
|
||||
func runCmd(cfg config.Config) error {
|
||||
logLevel, err := zerolog.ParseLevel(strings.ToLower(cfg.LogLevel))
|
||||
logger := tlog.NewLogger(cfg.Log)
|
||||
logger.Init()
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Invalid or missing log level, defaulting to info")
|
||||
} else {
|
||||
zerolog.SetGlobalLevel(logLevel)
|
||||
}
|
||||
|
||||
log.Logger = log.With().Caller().Logger()
|
||||
|
||||
if !cfg.LogJSON {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
|
||||
}
|
||||
|
||||
log.Info().Str("version", config.Version).Msg("Starting tinyauth")
|
||||
tlog.App.Info().Str("version", config.Version).Msg("Starting tinyauth")
|
||||
|
||||
app := bootstrap.NewBootstrapApp(cfg)
|
||||
|
||||
err = app.Setup()
|
||||
err := app.Setup()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to bootstrap app: %w", err)
|
||||
|
||||
@@ -3,15 +3,12 @@ package main
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/paerser/cli"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -47,7 +44,7 @@ func verifyUserCmd() *cli.Command {
|
||||
Configuration: tCfg,
|
||||
Resources: loaders,
|
||||
Run: func(_ []string) error {
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).With().Caller().Logger().Level(zerolog.InfoLevel)
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
if tCfg.Interactive {
|
||||
form := huh.NewForm(
|
||||
@@ -101,9 +98,9 @@ func verifyUserCmd() *cli.Command {
|
||||
|
||||
if user.TotpSecret == "" {
|
||||
if tCfg.Totp != "" {
|
||||
log.Warn().Msg("User does not have TOTP secret")
|
||||
tlog.App.Warn().Msg("User does not have TOTP secret")
|
||||
}
|
||||
log.Info().Msg("User verified")
|
||||
tlog.App.Info().Msg("User verified")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -113,7 +110,7 @@ func verifyUserCmd() *cli.Command {
|
||||
return fmt.Errorf("TOTP code incorrect")
|
||||
}
|
||||
|
||||
log.Info().Msg("User verified")
|
||||
tlog.App.Info().Msg("User verified")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
# The base URL where Tinyauth is accessible
|
||||
appUrl: "https://auth.example.com"
|
||||
# Log level: trace, debug, info, warn, error
|
||||
logLevel: "info"
|
||||
# Directory for static resources
|
||||
resourcesDir: "./resources"
|
||||
# Path to SQLite database file
|
||||
@@ -14,8 +12,22 @@ disableAnalytics: false
|
||||
disableResources: false
|
||||
# Disable UI warning messages
|
||||
disableUIWarnings: false
|
||||
# Enable JSON formatted logs
|
||||
logJSON: false
|
||||
|
||||
# Logging Configuration
|
||||
log:
|
||||
# Log level: trace, debug, info, warn, error
|
||||
level: "info"
|
||||
json: false
|
||||
streams:
|
||||
app:
|
||||
enabled: true
|
||||
level: "warn"
|
||||
http:
|
||||
enabled: true
|
||||
level: "debug"
|
||||
audit:
|
||||
enabled: false
|
||||
level: "info"
|
||||
|
||||
# Server Configuration
|
||||
server:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.90.16",
|
||||
"@tanstack/react-query": "^5.90.17",
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -24,8 +24,8 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-hook-form": "^7.71.0",
|
||||
"react-i18next": "^16.5.2",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-i18next": "^16.5.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.12.0",
|
||||
"sonner": "^2.0.7",
|
||||
@@ -36,7 +36,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tanstack/eslint-plugin-query": "^5.91.2",
|
||||
"@types/node": "^25.0.7",
|
||||
"@types/node": "^25.0.8",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
@@ -44,7 +44,7 @@
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^17.0.0",
|
||||
"prettier": "3.7.4",
|
||||
"prettier": "3.8.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.53.0",
|
||||
@@ -339,9 +339,9 @@
|
||||
|
||||
"@tanstack/eslint-plugin-query": ["@tanstack/eslint-plugin-query@5.91.2", "", { "dependencies": { "@typescript-eslint/utils": "^8.44.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" } }, "sha512-UPeWKl/Acu1IuuHJlsN+eITUHqAaa9/04geHHPedY8siVarSaWprY0SVMKrkpKfk5ehRT7+/MZ5QwWuEtkWrFw=="],
|
||||
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.90.16", "", {}, "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww=="],
|
||||
"@tanstack/query-core": ["@tanstack/query-core@5.90.17", "", {}, "sha512-hDww+RyyYhjhUfoYQ4es6pbgxY7LNiPWxt4l1nJqhByjndxJ7HIjDxTBtfvMr5HwjYavMrd+ids5g4Rfev3lVQ=="],
|
||||
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.90.16", "", { "dependencies": { "@tanstack/query-core": "5.90.16" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ=="],
|
||||
"@tanstack/react-query": ["@tanstack/react-query@5.90.17", "", { "dependencies": { "@tanstack/query-core": "5.90.17" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-PGc2u9KLwohDUSchjW9MZqeDQJfJDON7y4W7REdNBgiFKxQy+Pf7eGjiFWEj5xPqKzAeHYdAb62IWI1a9UJyGQ=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.0.7", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w=="],
|
||||
"@types/node": ["@types/node@25.0.8", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.8", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg=="],
|
||||
|
||||
@@ -781,7 +781,7 @@
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
|
||||
"prettier": ["prettier@3.8.0", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
@@ -795,9 +795,9 @@
|
||||
|
||||
"react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
|
||||
|
||||
"react-hook-form": ["react-hook-form@7.71.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-oFDt/iIFMV9ZfV52waONXzg4xuSlbwKUPvXVH2jumL1me5qFhBMc4knZxuXiZ2+j6h546sYe3ZKJcg/900/iHw=="],
|
||||
"react-hook-form": ["react-hook-form@7.71.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w=="],
|
||||
|
||||
"react-i18next": ["react-i18next@16.5.2", "", { "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-GG/SBVxx9dvrO1uCs8VYdKfOP8NEBUhNP+2VDQLCifRJ8DL1qPq296k2ACNGyZMDe7iyIlz/LMJTQOs8HXSRvw=="],
|
||||
"react-i18next": ["react-i18next@16.5.3", "", { "dependencies": { "@babel/runtime": "^7.28.4", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-fo+/NNch37zqxOzlBYrWMx0uy/yInPkRfjSuy4lqKdaecR17nvCHnEUt3QyzA8XjQ2B/0iW/5BhaHR3ZmukpGw=="],
|
||||
|
||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-query": "^5.90.16",
|
||||
"@tanstack/react-query": "^5.90.17",
|
||||
"axios": "^1.13.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -30,8 +30,8 @@
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-hook-form": "^7.71.0",
|
||||
"react-i18next": "^16.5.2",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-i18next": "^16.5.3",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^7.12.0",
|
||||
"sonner": "^2.0.7",
|
||||
@@ -42,7 +42,7 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@tanstack/eslint-plugin-query": "^5.91.2",
|
||||
"@types/node": "^25.0.7",
|
||||
"@types/node": "^25.0.8",
|
||||
"@types/react": "^19.2.8",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
@@ -50,7 +50,7 @@
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"globals": "^17.0.0",
|
||||
"prettier": "3.7.4",
|
||||
"prettier": "3.8.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.53.0",
|
||||
|
||||
8
go.mod
8
go.mod
@@ -21,10 +21,10 @@ require (
|
||||
github.com/traefik/paerser v0.2.2
|
||||
github.com/weppos/publicsuffix-go v0.50.2
|
||||
golang.org/x/crypto v0.47.0
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546
|
||||
golang.org/x/oauth2 v0.34.0
|
||||
gotest.tools/v3 v3.5.2
|
||||
modernc.org/sqlite v1.43.0
|
||||
modernc.org/sqlite v1.44.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -91,7 +91,7 @@ require (
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
@@ -119,7 +119,7 @@ require (
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/libc v1.67.4 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
|
||||
28
go.sum
28
go.sum
@@ -140,6 +140,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3Ar
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
@@ -215,8 +217,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -301,8 +303,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
@@ -369,18 +371,20 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||
modernc.org/libc v1.67.4 h1:zZGmCMUVPORtKv95c2ReQN5VDjvkoRm9GWPTEPuvlWg=
|
||||
modernc.org/libc v1.67.4/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -389,8 +393,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.43.0 h1:8YqiFx3G1VhHTXO2Q00bl1Wz9KhS9Q5okwfp9Y97VnA=
|
||||
modernc.org/sqlite v1.43.0/go.mod h1:+VkC6v3pLOAE0A0uVucQEcbVW0I5nHCeDaBf+DpsQT8=
|
||||
modernc.org/sqlite v1.44.0 h1:YjCKJnzZde2mLVy0cMKTSL4PxCmbIguOq9lGp8ZvGOc=
|
||||
modernc.org/sqlite v1.44.0/go.mod h1:2Dq41ir5/qri7QJJJKNZcP4UF7TsX/KNeykYgPDtGhE=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -16,8 +16,7 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type BootstrapApp struct {
|
||||
@@ -103,13 +102,13 @@ func (app *BootstrapApp) Setup() error {
|
||||
app.context.redirectCookieName = fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId)
|
||||
|
||||
// Dumps
|
||||
log.Trace().Interface("config", app.config).Msg("Config dump")
|
||||
log.Trace().Interface("users", app.context.users).Msg("Users dump")
|
||||
log.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
|
||||
log.Trace().Str("cookieDomain", app.context.cookieDomain).Msg("Cookie domain")
|
||||
log.Trace().Str("sessionCookieName", app.context.sessionCookieName).Msg("Session cookie name")
|
||||
log.Trace().Str("csrfCookieName", app.context.csrfCookieName).Msg("CSRF cookie name")
|
||||
log.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
||||
tlog.App.Trace().Interface("config", app.config).Msg("Config dump")
|
||||
tlog.App.Trace().Interface("users", app.context.users).Msg("Users dump")
|
||||
tlog.App.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
|
||||
tlog.App.Trace().Str("cookieDomain", app.context.cookieDomain).Msg("Cookie domain")
|
||||
tlog.App.Trace().Str("sessionCookieName", app.context.sessionCookieName).Msg("Session cookie name")
|
||||
tlog.App.Trace().Str("csrfCookieName", app.context.csrfCookieName).Msg("CSRF cookie name")
|
||||
tlog.App.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
||||
|
||||
// Database
|
||||
db, err := app.SetupDatabase(app.config.DatabasePath)
|
||||
@@ -153,7 +152,7 @@ func (app *BootstrapApp) Setup() error {
|
||||
})
|
||||
}
|
||||
|
||||
log.Debug().Interface("providers", configuredProviders).Msg("Authentication providers")
|
||||
tlog.App.Debug().Interface("providers", configuredProviders).Msg("Authentication providers")
|
||||
|
||||
if len(configuredProviders) == 0 {
|
||||
return fmt.Errorf("no authentication providers configured")
|
||||
@@ -169,28 +168,28 @@ func (app *BootstrapApp) Setup() error {
|
||||
}
|
||||
|
||||
// Start db cleanup routine
|
||||
log.Debug().Msg("Starting database cleanup routine")
|
||||
tlog.App.Debug().Msg("Starting database cleanup routine")
|
||||
go app.dbCleanup(queries)
|
||||
|
||||
// If analytics are not disabled, start heartbeat
|
||||
if !app.config.DisableAnalytics {
|
||||
log.Debug().Msg("Starting heartbeat routine")
|
||||
tlog.App.Debug().Msg("Starting heartbeat routine")
|
||||
go app.heartbeat()
|
||||
}
|
||||
|
||||
// If we have an socket path, bind to it
|
||||
if app.config.Server.SocketPath != "" {
|
||||
if _, err := os.Stat(app.config.Server.SocketPath); err == nil {
|
||||
log.Info().Msgf("Removing existing socket file %s", app.config.Server.SocketPath)
|
||||
tlog.App.Info().Msgf("Removing existing socket file %s", app.config.Server.SocketPath)
|
||||
err := os.Remove(app.config.Server.SocketPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove existing socket file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msgf("Starting server on unix socket %s", app.config.Server.SocketPath)
|
||||
tlog.App.Info().Msgf("Starting server on unix socket %s", app.config.Server.SocketPath)
|
||||
if err := router.RunUnix(app.config.Server.SocketPath); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start server")
|
||||
tlog.App.Fatal().Err(err).Msg("Failed to start server")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -198,9 +197,9 @@ func (app *BootstrapApp) Setup() error {
|
||||
|
||||
// Start server
|
||||
address := fmt.Sprintf("%s:%d", app.config.Server.Address, app.config.Server.Port)
|
||||
log.Info().Msgf("Starting server on %s", address)
|
||||
tlog.App.Info().Msgf("Starting server on %s", address)
|
||||
if err := router.Run(address); err != nil {
|
||||
log.Fatal().Err(err).Msg("Failed to start server")
|
||||
tlog.App.Fatal().Err(err).Msg("Failed to start server")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -223,7 +222,7 @@ func (app *BootstrapApp) heartbeat() {
|
||||
bodyJson, err := json.Marshal(body)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal heartbeat body")
|
||||
tlog.App.Error().Err(err).Msg("Failed to marshal heartbeat body")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -234,12 +233,12 @@ func (app *BootstrapApp) heartbeat() {
|
||||
heartbeatURL := config.ApiServer + "/v1/instances/heartbeat"
|
||||
|
||||
for ; true; <-ticker.C {
|
||||
log.Debug().Msg("Sending heartbeat")
|
||||
tlog.App.Debug().Msg("Sending heartbeat")
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, heartbeatURL, bytes.NewReader(bodyJson))
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create heartbeat request")
|
||||
tlog.App.Error().Err(err).Msg("Failed to create heartbeat request")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -248,14 +247,14 @@ func (app *BootstrapApp) heartbeat() {
|
||||
res, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to send heartbeat")
|
||||
tlog.App.Error().Err(err).Msg("Failed to send heartbeat")
|
||||
continue
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 && res.StatusCode != 201 {
|
||||
log.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
||||
tlog.App.Debug().Str("status", res.Status).Msg("Heartbeat returned non-200/201 status")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,10 +265,10 @@ func (app *BootstrapApp) dbCleanup(queries *repository.Queries) {
|
||||
ctx := context.Background()
|
||||
|
||||
for ; true; <-ticker.C {
|
||||
log.Debug().Msg("Cleaning up old database sessions")
|
||||
tlog.App.Debug().Msg("Cleaning up old database sessions")
|
||||
err := queries.DeleteExpiredSessions(ctx, time.Now().Unix())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to clean up old database sessions")
|
||||
tlog.App.Error().Err(err).Msg("Failed to clean up old database sessions")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ package bootstrap
|
||||
import (
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type Services struct {
|
||||
@@ -34,7 +33,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
|
||||
if err == nil {
|
||||
services.ldapService = ldapService
|
||||
} else {
|
||||
log.Warn().Err(err).Msg("Failed to initialize LDAP service, continuing without it")
|
||||
tlog.App.Warn().Err(err).Msg("Failed to initialize LDAP service, continuing without it")
|
||||
}
|
||||
|
||||
dockerService := service.NewDockerService()
|
||||
|
||||
@@ -16,13 +16,11 @@ var RedirectCookieName = "tinyauth-redirect"
|
||||
|
||||
type Config struct {
|
||||
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
||||
LogLevel string `description:"Log level (trace, debug, info, warn, error)." yaml:"logLevel"`
|
||||
ResourcesDir string `description:"The directory where resources are stored." yaml:"resourcesDir"`
|
||||
DatabasePath string `description:"The path to the database file." yaml:"databasePath"`
|
||||
DisableAnalytics bool `description:"Disable analytics." yaml:"disableAnalytics"`
|
||||
DisableResources bool `description:"Disable resources server." yaml:"disableResources"`
|
||||
DisableUIWarnings bool `description:"Disable UI warnings." yaml:"disableUIWarnings"`
|
||||
LogJSON bool `description:"Enable JSON formatted logs." yaml:"logJSON"`
|
||||
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
||||
@@ -30,6 +28,7 @@ type Config struct {
|
||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
@@ -78,6 +77,23 @@ type LdapConfig struct {
|
||||
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `description:"Log level (trace, debug, info, warn, error)." yaml:"level"`
|
||||
Json bool `description:"Enable JSON formatted logs." yaml:"json"`
|
||||
Streams LogStreams `description:"Configuration for specific log streams." yaml:"streams"`
|
||||
}
|
||||
|
||||
type LogStreams struct {
|
||||
HTTP LogStreamConfig `description:"HTTP request logging." yaml:"http"`
|
||||
App LogStreamConfig `description:"Application logging." yaml:"app"`
|
||||
Audit LogStreamConfig `description:"Audit logging." yaml:"audit"`
|
||||
}
|
||||
|
||||
type LogStreamConfig struct {
|
||||
Enabled bool `description:"Enable this log stream." yaml:"enabled"`
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type UserContextResponse struct {
|
||||
@@ -61,7 +61,7 @@ type ContextController struct {
|
||||
|
||||
func NewContextController(config ContextControllerConfig, router *gin.RouterGroup) *ContextController {
|
||||
if config.DisableUIWarnings {
|
||||
log.Warn().Msg("UI warnings are disabled. This may expose users to security risks. Proceed with caution.")
|
||||
tlog.App.Warn().Msg("UI warnings are disabled. This may expose users to security risks. Proceed with caution.")
|
||||
}
|
||||
|
||||
return &ContextController{
|
||||
@@ -94,7 +94,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("No user context found in request")
|
||||
tlog.App.Debug().Err(err).Msg("No user context found in request")
|
||||
userContext.Status = 401
|
||||
userContext.Message = "Unauthorized"
|
||||
userContext.IsLoggedIn = false
|
||||
@@ -108,7 +108,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
|
||||
func (controller *ContextController) appContextHandler(c *gin.Context) {
|
||||
appUrl, err := url.Parse(controller.config.AppURL)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse app URL")
|
||||
tlog.App.Error().Err(err).Msg("Failed to parse app URL")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -48,6 +49,8 @@ var userContext = config.UserContext{
|
||||
}
|
||||
|
||||
func setupContextController(middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) {
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
// Setup
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.Default()
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type OAuthRequest struct {
|
||||
@@ -55,7 +55,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
|
||||
err := c.BindUri(&req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to bind URI")
|
||||
tlog.App.Error().Err(err).Msg("Failed to bind URI")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -66,7 +66,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
service, exists := controller.broker.GetService(req.Provider)
|
||||
|
||||
if !exists {
|
||||
log.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
tlog.App.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
c.JSON(404, gin.H{
|
||||
"status": 404,
|
||||
"message": "Not Found",
|
||||
@@ -83,12 +83,12 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
isRedirectSafe := utils.IsRedirectSafe(redirectURI, controller.config.CookieDomain)
|
||||
|
||||
if !isRedirectSafe {
|
||||
log.Warn().Str("redirect_uri", redirectURI).Msg("Unsafe redirect URI detected, ignoring")
|
||||
tlog.App.Warn().Str("redirect_uri", redirectURI).Msg("Unsafe redirect URI detected, ignoring")
|
||||
redirectURI = ""
|
||||
}
|
||||
|
||||
if redirectURI != "" && isRedirectSafe {
|
||||
log.Debug().Msg("Setting redirect URI cookie")
|
||||
tlog.App.Debug().Msg("Setting redirect URI cookie")
|
||||
c.SetCookie(controller.config.RedirectCookieName, redirectURI, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
|
||||
err := c.BindUri(&req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to bind URI")
|
||||
tlog.App.Error().Err(err).Msg("Failed to bind URI")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -116,7 +116,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
csrfCookie, err := c.Cookie(controller.config.CSRFCookieName)
|
||||
|
||||
if err != nil || state != csrfCookie {
|
||||
log.Warn().Err(err).Msg("CSRF token mismatch or cookie missing")
|
||||
tlog.App.Warn().Err(err).Msg("CSRF token mismatch or cookie missing")
|
||||
c.SetCookie(controller.config.CSRFCookieName, "", -1, "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
@@ -128,14 +128,14 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
service, exists := controller.broker.GetService(req.Provider)
|
||||
|
||||
if !exists {
|
||||
log.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
tlog.App.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
err = service.VerifyCode(code)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to verify OAuth code")
|
||||
tlog.App.Error().Err(err).Msg("Failed to verify OAuth code")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -143,26 +143,27 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
user, err := controller.broker.GetUser(req.Provider)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get user from OAuth provider")
|
||||
tlog.App.Error().Err(err).Msg("Failed to get user from OAuth provider")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
if user.Email == "" {
|
||||
log.Error().Msg("OAuth provider did not return an email")
|
||||
tlog.App.Error().Msg("OAuth provider did not return an email")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
if !controller.auth.IsEmailWhitelisted(user.Email) {
|
||||
log.Warn().Str("email", user.Email).Msg("Email not whitelisted")
|
||||
tlog.App.Warn().Str("email", user.Email).Msg("Email not whitelisted")
|
||||
tlog.AuditLoginFailure(c, user.Email, req.Provider, "email not whitelisted")
|
||||
|
||||
queries, err := query.Values(config.UnauthorizedQuery{
|
||||
Username: user.Email,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -174,20 +175,20 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
var name string
|
||||
|
||||
if strings.TrimSpace(user.Name) != "" {
|
||||
log.Debug().Msg("Using name from OAuth provider")
|
||||
tlog.App.Debug().Msg("Using name from OAuth provider")
|
||||
name = user.Name
|
||||
} else {
|
||||
log.Debug().Msg("No name from OAuth provider, using pseudo name")
|
||||
tlog.App.Debug().Msg("No name from OAuth provider, using pseudo name")
|
||||
name = fmt.Sprintf("%s (%s)", utils.Capitalize(strings.Split(user.Email, "@")[0]), strings.Split(user.Email, "@")[1])
|
||||
}
|
||||
|
||||
var username string
|
||||
|
||||
if strings.TrimSpace(user.PreferredUsername) != "" {
|
||||
log.Debug().Msg("Using preferred username from OAuth provider")
|
||||
tlog.App.Debug().Msg("Using preferred username from OAuth provider")
|
||||
username = user.PreferredUsername
|
||||
} else {
|
||||
log.Debug().Msg("No preferred username from OAuth provider, using pseudo username")
|
||||
tlog.App.Debug().Msg("No preferred username from OAuth provider, using pseudo username")
|
||||
username = strings.Replace(user.Email, "@", "_", -1)
|
||||
}
|
||||
|
||||
@@ -201,20 +202,22 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
OAuthSub: user.Sub,
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create session cookie")
|
||||
tlog.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
tlog.AuditLoginSuccess(c, sessionCookie.Username, sessionCookie.Provider)
|
||||
|
||||
redirectURI, err := c.Cookie(controller.config.RedirectCookieName)
|
||||
|
||||
if err != nil || !utils.IsRedirectSafe(redirectURI, controller.config.CookieDomain) {
|
||||
log.Debug().Msg("No redirect URI cookie found, redirecting to app root")
|
||||
tlog.App.Debug().Msg("No redirect URI cookie found, redirecting to app root")
|
||||
c.Redirect(http.StatusTemporaryRedirect, controller.config.AppURL)
|
||||
return
|
||||
}
|
||||
@@ -224,7 +227,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode redirect URI query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode redirect URI query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/go-querystring/query"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var SupportedProxies = []string{"nginx", "traefik", "caddy", "envoy"}
|
||||
@@ -52,7 +52,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
|
||||
err := c.BindUri(&req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to bind URI")
|
||||
tlog.App.Error().Err(err).Msg("Failed to bind URI")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -61,7 +61,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !slices.Contains(SupportedProxies, req.Proxy) {
|
||||
log.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
|
||||
tlog.App.Warn().Str("proxy", req.Proxy).Msg("Invalid proxy")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -73,7 +73,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
// Envoy uses the original client method for the external auth request
|
||||
// so we allow Any standard HTTP method for /api/auth/envoy
|
||||
if req.Proxy != "envoy" && c.Request.Method != http.MethodGet {
|
||||
log.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
|
||||
tlog.App.Warn().Str("method", c.Request.Method).Msg("Invalid method for proxy")
|
||||
c.Header("Allow", "GET")
|
||||
c.JSON(405, gin.H{
|
||||
"status": 405,
|
||||
@@ -85,9 +85,9 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
isBrowser := strings.Contains(c.Request.Header.Get("Accept"), "text/html")
|
||||
|
||||
if isBrowser {
|
||||
log.Debug().Msg("Request identified as (most likely) coming from a browser")
|
||||
tlog.App.Debug().Msg("Request identified as (most likely) coming from a browser")
|
||||
} else {
|
||||
log.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
|
||||
tlog.App.Debug().Msg("Request identified as (most likely) coming from a non-browser client")
|
||||
}
|
||||
|
||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||
@@ -98,12 +98,12 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
acls, err := controller.acls.GetAccessControls(host)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get access controls for resource")
|
||||
tlog.App.Error().Err(err).Msg("Failed to get access controls for resource")
|
||||
controller.handleError(c, req, isBrowser)
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace().Interface("acls", acls).Msg("ACLs for resource")
|
||||
tlog.App.Trace().Interface("acls", acls).Msg("ACLs for resource")
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
@@ -119,13 +119,13 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
authEnabled, err := controller.auth.IsAuthEnabled(uri, acls.Path)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
|
||||
tlog.App.Error().Err(err).Msg("Failed to check if auth is enabled for resource")
|
||||
controller.handleError(c, req, isBrowser)
|
||||
return
|
||||
}
|
||||
|
||||
if !authEnabled {
|
||||
log.Debug().Msg("Authentication disabled for resource, allowing access")
|
||||
tlog.App.Debug().Msg("Authentication disabled for resource, allowing access")
|
||||
controller.setHeaders(c, acls)
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
@@ -149,7 +149,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
context, err := utils.GetContext(c)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Msg("No user context found in request, treating as not logged in")
|
||||
tlog.App.Debug().Msg("No user context found in request, treating as not logged in")
|
||||
userContext = config.UserContext{
|
||||
IsLoggedIn: false,
|
||||
}
|
||||
@@ -171,10 +171,10 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
userContext = context
|
||||
}
|
||||
|
||||
log.Trace().Interface("context", userContext).Msg("User context from request")
|
||||
tlog.App.Trace().Interface("context", userContext).Msg("User context from request")
|
||||
|
||||
if userContext.Provider == "basic" && userContext.TotpEnabled {
|
||||
log.Debug().Msg("User has TOTP enabled, denying basic auth access")
|
||||
tlog.App.Debug().Msg("User has TOTP enabled, denying basic auth access")
|
||||
userContext.IsLoggedIn = false
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
userAllowed := controller.auth.IsUserAllowed(c, userContext, acls)
|
||||
|
||||
if !userAllowed {
|
||||
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
|
||||
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User not allowed to access resource")
|
||||
|
||||
if req.Proxy == "nginx" || !isBrowser {
|
||||
c.JSON(403, gin.H{
|
||||
@@ -197,7 +197,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -216,7 +216,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
groupOK := controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
|
||||
|
||||
if !groupOK {
|
||||
log.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
|
||||
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
|
||||
|
||||
if req.Proxy == "nginx" || !isBrowser {
|
||||
c.JSON(403, gin.H{
|
||||
@@ -232,7 +232,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode unauthorized query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -276,7 +276,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode redirect URI query")
|
||||
tlog.App.Error().Err(err).Msg("Failed to encode redirect URI query")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
@@ -290,14 +290,14 @@ func (controller *ProxyController) setHeaders(c *gin.Context, acls config.App) {
|
||||
headers := utils.ParseHeaders(acls.Response.Headers)
|
||||
|
||||
for key, value := range headers {
|
||||
log.Debug().Str("header", key).Msg("Setting header")
|
||||
tlog.App.Debug().Str("header", key).Msg("Setting header")
|
||||
c.Header(key, value)
|
||||
}
|
||||
|
||||
basicPassword := utils.GetSecret(acls.Response.BasicAuth.Password, acls.Response.BasicAuth.PasswordFile)
|
||||
|
||||
if acls.Response.BasicAuth.Username != "" && basicPassword != "" {
|
||||
log.Debug().Str("username", acls.Response.BasicAuth.Username).Msg("Setting basic auth header")
|
||||
tlog.App.Debug().Str("username", acls.Response.BasicAuth.Username).Msg("Setting basic auth header")
|
||||
c.Header("Authorization", fmt.Sprintf("Basic %s", utils.GetBasicAuth(acls.Response.BasicAuth.Username, basicPassword)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func setupProxyController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder, *service.AuthService) {
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
// Setup
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.Default()
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
@@ -53,7 +53,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to bind JSON")
|
||||
tlog.App.Error().Err(err).Msg("Failed to bind JSON")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -61,12 +61,13 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().Str("username", req.Username).Msg("Login attempt")
|
||||
tlog.App.Debug().Str("username", req.Username).Msg("Login attempt")
|
||||
|
||||
isLocked, remaining := controller.auth.IsAccountLocked(req.Username)
|
||||
|
||||
if isLocked {
|
||||
log.Warn().Str("username", req.Username).Msg("Account is locked due to too many failed login attempts")
|
||||
tlog.App.Warn().Str("username", req.Username).Msg("Account is locked due to too many failed login attempts")
|
||||
tlog.AuditLoginFailure(c, req.Username, "username", "account locked")
|
||||
c.Writer.Header().Add("x-tinyauth-lock-locked", "true")
|
||||
c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339))
|
||||
c.JSON(429, gin.H{
|
||||
@@ -79,8 +80,9 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
userSearch := controller.auth.SearchUser(req.Username)
|
||||
|
||||
if userSearch.Type == "unknown" {
|
||||
log.Warn().Str("username", req.Username).Msg("User not found")
|
||||
tlog.App.Warn().Str("username", req.Username).Msg("User not found")
|
||||
controller.auth.RecordLoginAttempt(req.Username, false)
|
||||
tlog.AuditLoginFailure(c, req.Username, "username", "user not found")
|
||||
c.JSON(401, gin.H{
|
||||
"status": 401,
|
||||
"message": "Unauthorized",
|
||||
@@ -89,8 +91,9 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !controller.auth.VerifyUser(userSearch, req.Password) {
|
||||
log.Warn().Str("username", req.Username).Msg("Invalid password")
|
||||
tlog.App.Warn().Str("username", req.Username).Msg("Invalid password")
|
||||
controller.auth.RecordLoginAttempt(req.Username, false)
|
||||
tlog.AuditLoginFailure(c, req.Username, "username", "invalid password")
|
||||
c.JSON(401, gin.H{
|
||||
"status": 401,
|
||||
"message": "Unauthorized",
|
||||
@@ -98,7 +101,8 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("username", req.Username).Msg("Login successful")
|
||||
tlog.App.Info().Str("username", req.Username).Msg("Login successful")
|
||||
tlog.AuditLoginSuccess(c, req.Username, "username")
|
||||
|
||||
controller.auth.RecordLoginAttempt(req.Username, true)
|
||||
|
||||
@@ -106,7 +110,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
user := controller.auth.GetLocalUser(userSearch.Username)
|
||||
|
||||
if user.TotpSecret != "" {
|
||||
log.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")
|
||||
tlog.App.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")
|
||||
|
||||
err := controller.auth.CreateSessionCookie(c, &repository.Session{
|
||||
Username: user.Username,
|
||||
@@ -117,7 +121,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create session cookie")
|
||||
tlog.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
@@ -145,7 +149,7 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
ldapUser, err := controller.auth.GetLdapUser(userSearch.Username)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("username", req.Username).Msg("Failed to get LDAP user details")
|
||||
tlog.App.Error().Err(err).Str("username", req.Username).Msg("Failed to get LDAP user details")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
@@ -156,12 +160,12 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
sessionCookie.LdapGroups = strings.Join(ldapUser.Groups, ",")
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create session cookie")
|
||||
tlog.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
@@ -176,10 +180,15 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (controller *UserController) logoutHandler(c *gin.Context) {
|
||||
log.Debug().Msg("Logout request received")
|
||||
tlog.App.Debug().Msg("Logout request received")
|
||||
|
||||
controller.auth.DeleteSessionCookie(c)
|
||||
|
||||
context, err := utils.GetContext(c)
|
||||
if err == nil && context.IsLoggedIn {
|
||||
tlog.AuditLogout(c, context.Username, context.Provider)
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"message": "Logout successful",
|
||||
@@ -191,7 +200,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to bind JSON")
|
||||
tlog.App.Error().Err(err).Msg("Failed to bind JSON")
|
||||
c.JSON(400, gin.H{
|
||||
"status": 400,
|
||||
"message": "Bad Request",
|
||||
@@ -202,7 +211,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
context, err := utils.GetContext(c)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get user context")
|
||||
tlog.App.Error().Err(err).Msg("Failed to get user context")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
@@ -211,7 +220,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !context.TotpPending {
|
||||
log.Warn().Msg("TOTP attempt without a pending TOTP session")
|
||||
tlog.App.Warn().Msg("TOTP attempt without a pending TOTP session")
|
||||
c.JSON(401, gin.H{
|
||||
"status": 401,
|
||||
"message": "Unauthorized",
|
||||
@@ -219,12 +228,12 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug().Str("username", context.Username).Msg("TOTP verification attempt")
|
||||
tlog.App.Debug().Str("username", context.Username).Msg("TOTP verification attempt")
|
||||
|
||||
isLocked, remaining := controller.auth.IsAccountLocked(context.Username)
|
||||
|
||||
if isLocked {
|
||||
log.Warn().Str("username", context.Username).Msg("Account is locked due to too many failed TOTP attempts")
|
||||
tlog.App.Warn().Str("username", context.Username).Msg("Account is locked due to too many failed TOTP attempts")
|
||||
c.Writer.Header().Add("x-tinyauth-lock-locked", "true")
|
||||
c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339))
|
||||
c.JSON(429, gin.H{
|
||||
@@ -239,8 +248,9 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
ok := totp.Validate(req.Code, user.TotpSecret)
|
||||
|
||||
if !ok {
|
||||
log.Warn().Str("username", context.Username).Msg("Invalid TOTP code")
|
||||
tlog.App.Warn().Str("username", context.Username).Msg("Invalid TOTP code")
|
||||
controller.auth.RecordLoginAttempt(context.Username, false)
|
||||
tlog.AuditLoginFailure(c, context.Username, "totp", "invalid totp code")
|
||||
c.JSON(401, gin.H{
|
||||
"status": 401,
|
||||
"message": "Unauthorized",
|
||||
@@ -248,7 +258,8 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("username", context.Username).Msg("TOTP verification successful")
|
||||
tlog.App.Info().Str("username", context.Username).Msg("TOTP verification successful")
|
||||
tlog.AuditLoginSuccess(c, context.Username, "totp")
|
||||
|
||||
controller.auth.RecordLoginAttempt(context.Username, true)
|
||||
|
||||
@@ -259,12 +270,12 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
Provider: "username",
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
|
||||
err = controller.auth.CreateSessionCookie(c, &sessionCookie)
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create session cookie")
|
||||
tlog.App.Error().Err(err).Msg("Failed to create session cookie")
|
||||
c.JSON(500, gin.H{
|
||||
"status": 500,
|
||||
"message": "Internal Server Error",
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/controller"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pquerna/otp/totp"
|
||||
@@ -23,6 +24,8 @@ var cookieValue string
|
||||
var totpSecret = "6WFZXPEZRK5MZHHYAFW4DAOUYQMCASBJ"
|
||||
|
||||
func setupUserController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.Engine, *httptest.ResponseRecorder) {
|
||||
tlog.NewSimpleLogger().Init()
|
||||
|
||||
// Setup
|
||||
gin.SetMode(gin.TestMode)
|
||||
router := gin.Default()
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/service"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ContextMiddlewareConfig struct {
|
||||
@@ -40,7 +40,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
cookie, err := m.auth.GetSessionCookie(c)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("No valid session cookie found")
|
||||
tlog.App.Debug().Err(err).Msg("No valid session cookie found")
|
||||
goto basic
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
userSearch := m.auth.SearchUser(cookie.Username)
|
||||
|
||||
if userSearch.Type == "unknown" || userSearch.Type == "error" {
|
||||
log.Debug().Msg("User from session cookie not found")
|
||||
tlog.App.Debug().Msg("User from session cookie not found")
|
||||
m.auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
@@ -82,13 +82,13 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
_, exists := m.broker.GetService(cookie.Provider)
|
||||
|
||||
if !exists {
|
||||
log.Debug().Msg("OAuth provider from session cookie not found")
|
||||
tlog.App.Debug().Msg("OAuth provider from session cookie not found")
|
||||
m.auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
|
||||
if !m.auth.IsEmailWhitelisted(cookie.Email) {
|
||||
log.Debug().Msg("Email from session cookie not whitelisted")
|
||||
tlog.App.Debug().Msg("Email from session cookie not whitelisted")
|
||||
m.auth.DeleteSessionCookie(c)
|
||||
goto basic
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
basic := m.auth.GetBasicAuth(c)
|
||||
|
||||
if basic == nil {
|
||||
log.Debug().Msg("No basic auth provided")
|
||||
tlog.App.Debug().Msg("No basic auth provided")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
locked, remaining := m.auth.IsAccountLocked(basic.Username)
|
||||
|
||||
if locked {
|
||||
log.Debug().Msgf("Account for user %s is locked for %d seconds, denying auth", basic.Username, remaining)
|
||||
tlog.App.Debug().Msgf("Account for user %s is locked for %d seconds, denying auth", basic.Username, remaining)
|
||||
c.Writer.Header().Add("x-tinyauth-lock-locked", "true")
|
||||
c.Writer.Header().Add("x-tinyauth-lock-reset", time.Now().Add(time.Duration(remaining)*time.Second).Format(time.RFC3339))
|
||||
c.Next()
|
||||
@@ -132,14 +132,14 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
|
||||
if userSearch.Type == "unknown" || userSearch.Type == "error" {
|
||||
m.auth.RecordLoginAttempt(basic.Username, false)
|
||||
log.Debug().Msg("User from basic auth not found")
|
||||
tlog.App.Debug().Msg("User from basic auth not found")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if !m.auth.VerifyUser(userSearch, basic.Password) {
|
||||
m.auth.RecordLoginAttempt(basic.Username, false)
|
||||
log.Debug().Msg("Invalid password for basic auth user")
|
||||
tlog.App.Debug().Msg("Invalid password for basic auth user")
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (m *ContextMiddleware) Middleware() gin.HandlerFunc {
|
||||
|
||||
switch userSearch.Type {
|
||||
case "local":
|
||||
log.Debug().Msg("Basic auth user is local")
|
||||
tlog.App.Debug().Msg("Basic auth user is local")
|
||||
|
||||
user := m.auth.GetLocalUser(basic.Username)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -49,7 +49,7 @@ func (m *ZerologMiddleware) Middleware() gin.HandlerFunc {
|
||||
|
||||
latency := time.Since(tStart).String()
|
||||
|
||||
subLogger := log.With().Str("method", method).
|
||||
subLogger := tlog.HTTP.With().Str("method", method).
|
||||
Str("path", path).
|
||||
Str("address", address).
|
||||
Str("client_ip", clientIP).
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type AccessControlsService struct {
|
||||
@@ -27,12 +27,12 @@ func (acls *AccessControlsService) Init() error {
|
||||
func (acls *AccessControlsService) lookupStaticACLs(domain string) (config.App, error) {
|
||||
for app, config := range acls.static {
|
||||
if config.Config.Domain == domain {
|
||||
log.Debug().Str("name", app).Msg("Found matching container by domain")
|
||||
tlog.App.Debug().Str("name", app).Msg("Found matching container by domain")
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if strings.SplitN(domain, ".", 2)[0] == app {
|
||||
log.Debug().Str("name", app).Msg("Found matching container by app name")
|
||||
tlog.App.Debug().Str("name", app).Msg("Found matching container by app name")
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
@@ -44,11 +44,11 @@ func (acls *AccessControlsService) GetAccessControls(domain string) (config.App,
|
||||
app, err := acls.lookupStaticACLs(domain)
|
||||
|
||||
if err == nil {
|
||||
log.Debug().Msg("Using ACls from static configuration")
|
||||
tlog.App.Debug().Msg("Using ACls from static configuration")
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// Fallback to Docker labels
|
||||
log.Debug().Msg("Falling back to Docker labels for ACLs")
|
||||
tlog.App.Debug().Msg("Falling back to Docker labels for ACLs")
|
||||
return acls.docker.GetLabels(domain)
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/repository"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -73,7 +73,7 @@ func (auth *AuthService) SearchUser(username string) config.UserSearch {
|
||||
userDN, err := auth.ldap.GetUserDN(username)
|
||||
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP")
|
||||
tlog.App.Warn().Err(err).Str("username", username).Msg("Failed to search for user in LDAP")
|
||||
return config.UserSearch{
|
||||
Type: "error",
|
||||
}
|
||||
@@ -99,24 +99,24 @@ func (auth *AuthService) VerifyUser(search config.UserSearch, password string) b
|
||||
if auth.ldap != nil {
|
||||
err := auth.ldap.Bind(search.Username, password)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
|
||||
tlog.App.Warn().Err(err).Str("username", search.Username).Msg("Failed to bind to LDAP")
|
||||
return false
|
||||
}
|
||||
|
||||
err = auth.ldap.BindService(true)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
|
||||
tlog.App.Error().Err(err).Msg("Failed to rebind with service account after user authentication")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
default:
|
||||
log.Debug().Str("type", search.Type).Msg("Unknown user type for authentication")
|
||||
tlog.App.Debug().Str("type", search.Type).Msg("Unknown user type for authentication")
|
||||
return false
|
||||
}
|
||||
|
||||
log.Warn().Str("username", search.Username).Msg("User authentication failed")
|
||||
tlog.App.Warn().Str("username", search.Username).Msg("User authentication failed")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func (auth *AuthService) GetLocalUser(username string) config.User {
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn().Str("username", username).Msg("Local user not found")
|
||||
tlog.App.Warn().Str("username", username).Msg("Local user not found")
|
||||
return config.User{}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
|
||||
|
||||
if attempt.FailedAttempts >= auth.config.LoginMaxRetries {
|
||||
attempt.LockedUntil = time.Now().Add(time.Duration(auth.config.LoginTimeout) * time.Second)
|
||||
log.Warn().Str("identifier", identifier).Int("timeout", auth.config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
|
||||
tlog.App.Warn().Str("identifier", identifier).Int("timeout", auth.config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ func (auth *AuthService) RefreshSessionCookie(c *gin.Context) error {
|
||||
}
|
||||
|
||||
c.SetCookie(auth.config.SessionCookieName, cookie, int(newExpiry-currentTime), "/", fmt.Sprintf(".%s", auth.config.CookieDomain), auth.config.SecureCookie, true)
|
||||
log.Trace().Str("username", session.Username).Msg("Session cookie refreshed")
|
||||
tlog.App.Trace().Str("username", session.Username).Msg("Session cookie refreshed")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -337,7 +337,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
|
||||
if currentTime-session.CreatedAt > int64(auth.config.SessionMaxLifetime) {
|
||||
err = auth.queries.DeleteSession(c, cookie)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to delete session exceeding max lifetime")
|
||||
tlog.App.Error().Err(err).Msg("Failed to delete session exceeding max lifetime")
|
||||
}
|
||||
return repository.Session{}, fmt.Errorf("session expired due to max lifetime exceeded")
|
||||
}
|
||||
@@ -346,7 +346,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
|
||||
if currentTime > session.Expiry {
|
||||
err = auth.queries.DeleteSession(c, cookie)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to delete expired session")
|
||||
tlog.App.Error().Err(err).Msg("Failed to delete expired session")
|
||||
}
|
||||
return repository.Session{}, fmt.Errorf("session expired")
|
||||
}
|
||||
@@ -371,18 +371,18 @@ func (auth *AuthService) UserAuthConfigured() bool {
|
||||
|
||||
func (auth *AuthService) IsUserAllowed(c *gin.Context, context config.UserContext, acls config.App) bool {
|
||||
if context.OAuth {
|
||||
log.Debug().Msg("Checking OAuth whitelist")
|
||||
tlog.App.Debug().Msg("Checking OAuth whitelist")
|
||||
return utils.CheckFilter(acls.OAuth.Whitelist, context.Email)
|
||||
}
|
||||
|
||||
if acls.Users.Block != "" {
|
||||
log.Debug().Msg("Checking blocked users")
|
||||
tlog.App.Debug().Msg("Checking blocked users")
|
||||
if utils.CheckFilter(acls.Users.Block, context.Username) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msg("Checking users")
|
||||
tlog.App.Debug().Msg("Checking users")
|
||||
return utils.CheckFilter(acls.Users.Allow, context.Username)
|
||||
}
|
||||
|
||||
@@ -393,19 +393,19 @@ func (auth *AuthService) IsInOAuthGroup(c *gin.Context, context config.UserConte
|
||||
|
||||
for id := range config.OverrideProviders {
|
||||
if context.Provider == id {
|
||||
log.Info().Str("provider", id).Msg("OAuth groups not supported for this provider")
|
||||
tlog.App.Info().Str("provider", id).Msg("OAuth groups not supported for this provider")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for userGroup := range strings.SplitSeq(context.OAuthGroups, ",") {
|
||||
if utils.CheckFilter(requiredGroups, strings.TrimSpace(userGroup)) {
|
||||
log.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched")
|
||||
tlog.App.Trace().Str("group", userGroup).Str("required", requiredGroups).Msg("User group matched")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msg("No groups matched")
|
||||
tlog.App.Debug().Msg("No groups matched")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ func (auth *AuthService) IsAuthEnabled(uri string, path config.AppPath) (bool, e
|
||||
func (auth *AuthService) GetBasicAuth(c *gin.Context) *config.User {
|
||||
username, password, ok := c.Request.BasicAuth()
|
||||
if !ok {
|
||||
log.Debug().Msg("No basic auth provided")
|
||||
tlog.App.Debug().Msg("No basic auth provided")
|
||||
return nil
|
||||
}
|
||||
return &config.User{
|
||||
@@ -459,11 +459,11 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
for _, blocked := range blockedIps {
|
||||
res, err := utils.FilterIP(blocked, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
|
||||
tlog.App.Warn().Err(err).Str("item", blocked).Msg("Invalid IP/CIDR in block list")
|
||||
continue
|
||||
}
|
||||
if res {
|
||||
log.Debug().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
|
||||
tlog.App.Debug().Str("ip", ip).Str("item", blocked).Msg("IP is in blocked list, denying access")
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -471,21 +471,21 @@ func (auth *AuthService) CheckIP(acls config.AppIP, ip string) bool {
|
||||
for _, allowed := range allowedIPs {
|
||||
res, err := utils.FilterIP(allowed, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
|
||||
tlog.App.Warn().Err(err).Str("item", allowed).Msg("Invalid IP/CIDR in allow list")
|
||||
continue
|
||||
}
|
||||
if res {
|
||||
log.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
|
||||
tlog.App.Debug().Str("ip", ip).Str("item", allowed).Msg("IP is in allowed list, allowing access")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if len(allowedIPs) > 0 {
|
||||
log.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
|
||||
tlog.App.Debug().Str("ip", ip).Msg("IP not in allow list, denying access")
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
|
||||
tlog.App.Debug().Str("ip", ip).Msg("IP not in allow or block list, allowing by default")
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -493,15 +493,15 @@ func (auth *AuthService) IsBypassedIP(acls config.AppIP, ip string) bool {
|
||||
for _, bypassed := range acls.Bypass {
|
||||
res, err := utils.FilterIP(bypassed, ip)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
|
||||
tlog.App.Warn().Err(err).Str("item", bypassed).Msg("Invalid IP/CIDR in bypass list")
|
||||
continue
|
||||
}
|
||||
if res {
|
||||
log.Debug().Str("ip", ip).Str("item", bypassed).Msg("IP is in bypass list, allowing access")
|
||||
tlog.App.Debug().Str("ip", ip).Str("item", bypassed).Msg("IP is in bypass list, allowing access")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Str("ip", ip).Msg("IP not in bypass list, continuing with authentication")
|
||||
tlog.App.Debug().Str("ip", ip).Msg("IP not in bypass list, continuing with authentication")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/decoders"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
container "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type DockerService struct {
|
||||
@@ -37,7 +37,7 @@ func (docker *DockerService) Init() error {
|
||||
_, err = docker.client.Ping(docker.context)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Docker not connected")
|
||||
tlog.App.Debug().Err(err).Msg("Docker not connected")
|
||||
docker.isConnected = false
|
||||
docker.client = nil
|
||||
docker.context = nil
|
||||
@@ -45,7 +45,7 @@ func (docker *DockerService) Init() error {
|
||||
}
|
||||
|
||||
docker.isConnected = true
|
||||
log.Debug().Msg("Docker connected")
|
||||
tlog.App.Debug().Msg("Docker connected")
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -68,7 +68,7 @@ func (docker *DockerService) inspectContainer(containerId string) (container.Ins
|
||||
|
||||
func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
|
||||
if !docker.isConnected {
|
||||
log.Debug().Msg("Docker not connected, returning empty labels")
|
||||
tlog.App.Debug().Msg("Docker not connected, returning empty labels")
|
||||
return config.App{}, nil
|
||||
}
|
||||
|
||||
@@ -90,17 +90,17 @@ func (docker *DockerService) GetLabels(appDomain string) (config.App, error) {
|
||||
|
||||
for appName, appLabels := range labels.Apps {
|
||||
if appLabels.Config.Domain == appDomain {
|
||||
log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain")
|
||||
tlog.App.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by domain")
|
||||
return appLabels, nil
|
||||
}
|
||||
|
||||
if strings.SplitN(appDomain, ".", 2)[0] == appName {
|
||||
log.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name")
|
||||
tlog.App.Debug().Str("id", inspect.ID).Str("name", inspect.Name).Msg("Found matching container by app name")
|
||||
return appLabels, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug().Msg("No matching container found, returning empty labels")
|
||||
tlog.App.Debug().Msg("No matching container found, returning empty labels")
|
||||
return config.App{}, nil
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ func (generic *GenericOAuthService) Userinfo() (config.Claims, error) {
|
||||
return user, err
|
||||
}
|
||||
|
||||
log.Trace().Str("body", string(body)).Msg("Userinfo response body")
|
||||
tlog.App.Trace().Str("body", string(body)).Msg("Userinfo response body")
|
||||
|
||||
err = json.Unmarshal(body, &user)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
ldapgo "github.com/go-ldap/ldap/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
)
|
||||
|
||||
type LdapServiceConfig struct {
|
||||
@@ -46,7 +46,7 @@ func (ldap *LdapService) Init() error {
|
||||
return fmt.Errorf("failed to initialize LDAP with mTLS authentication: %w", err)
|
||||
}
|
||||
ldap.cert = &cert
|
||||
log.Info().Msg("Using LDAP with mTLS authentication")
|
||||
tlog.App.Info().Msg("Using LDAP with mTLS authentication")
|
||||
|
||||
// TODO: Add optional extra CA certificates, instead of `InsecureSkipVerify`
|
||||
/*
|
||||
@@ -68,12 +68,12 @@ func (ldap *LdapService) Init() error {
|
||||
for range time.Tick(time.Duration(5) * time.Minute) {
|
||||
err := ldap.heartbeat()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("LDAP connection heartbeat failed")
|
||||
tlog.App.Error().Err(err).Msg("LDAP connection heartbeat failed")
|
||||
if reconnectErr := ldap.reconnect(); reconnectErr != nil {
|
||||
log.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
|
||||
tlog.App.Error().Err(reconnectErr).Msg("Failed to reconnect to LDAP server")
|
||||
continue
|
||||
}
|
||||
log.Info().Msg("Successfully reconnected to LDAP server")
|
||||
tlog.App.Info().Msg("Successfully reconnected to LDAP server")
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -212,7 +212,7 @@ func (ldap *LdapService) Bind(userDN string, password string) error {
|
||||
}
|
||||
|
||||
func (ldap *LdapService) heartbeat() error {
|
||||
log.Debug().Msg("Performing LDAP connection heartbeat")
|
||||
tlog.App.Debug().Msg("Performing LDAP connection heartbeat")
|
||||
|
||||
searchRequest := ldapgo.NewSearchRequest(
|
||||
"",
|
||||
@@ -234,7 +234,7 @@ func (ldap *LdapService) heartbeat() error {
|
||||
}
|
||||
|
||||
func (ldap *LdapService) reconnect() error {
|
||||
log.Info().Msg("Reconnecting to LDAP server")
|
||||
tlog.App.Info().Msg("Reconnecting to LDAP server")
|
||||
|
||||
exp := backoff.NewExponentialBackOff()
|
||||
exp.InitialInterval = 500 * time.Millisecond
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
@@ -49,10 +49,10 @@ func (broker *OAuthBrokerService) Init() error {
|
||||
for name, service := range broker.services {
|
||||
err := service.Init()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Failed to initialize OAuth service: %T", name)
|
||||
tlog.App.Error().Err(err).Msgf("Failed to initialize OAuth service: %s", name)
|
||||
return err
|
||||
}
|
||||
log.Info().Str("service", name).Msg("Initialized OAuth service")
|
||||
tlog.App.Info().Str("service", name).Msg("Initialized OAuth service")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
39
internal/utils/tlog/log_audit.go
Normal file
39
internal/utils/tlog/log_audit.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package tlog
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
// functions here use CallerSkipFrame to ensure correct caller info is logged
|
||||
|
||||
func AuditLoginSuccess(c *gin.Context, username, provider string) {
|
||||
Audit.Info().
|
||||
CallerSkipFrame(1).
|
||||
Str("event", "login").
|
||||
Str("result", "success").
|
||||
Str("username", username).
|
||||
Str("provider", provider).
|
||||
Str("ip", c.ClientIP()).
|
||||
Send()
|
||||
}
|
||||
|
||||
func AuditLoginFailure(c *gin.Context, username, provider string, reason string) {
|
||||
Audit.Warn().
|
||||
CallerSkipFrame(1).
|
||||
Str("event", "login").
|
||||
Str("result", "failure").
|
||||
Str("username", username).
|
||||
Str("provider", provider).
|
||||
Str("ip", c.ClientIP()).
|
||||
Str("reason", reason).
|
||||
Send()
|
||||
}
|
||||
|
||||
func AuditLogout(c *gin.Context, username, provider string) {
|
||||
Audit.Info().
|
||||
CallerSkipFrame(1).
|
||||
Str("event", "logout").
|
||||
Str("result", "success").
|
||||
Str("username", username).
|
||||
Str("provider", provider).
|
||||
Str("ip", c.ClientIP()).
|
||||
Send()
|
||||
}
|
||||
86
internal/utils/tlog/log_wrapper.go
Normal file
86
internal/utils/tlog/log_wrapper.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package tlog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
Audit zerolog.Logger
|
||||
HTTP zerolog.Logger
|
||||
App zerolog.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
Audit zerolog.Logger
|
||||
HTTP zerolog.Logger
|
||||
App zerolog.Logger
|
||||
)
|
||||
|
||||
func NewLogger(cfg config.LogConfig) *Logger {
|
||||
baseLogger := log.With().
|
||||
Timestamp().
|
||||
Caller().
|
||||
Logger().
|
||||
Level(parseLogLevel(cfg.Level))
|
||||
|
||||
if !cfg.Json {
|
||||
baseLogger = baseLogger.Output(zerolog.ConsoleWriter{
|
||||
Out: os.Stderr,
|
||||
TimeFormat: time.RFC3339,
|
||||
})
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
Audit: createLogger("audit", cfg.Streams.Audit, baseLogger),
|
||||
HTTP: createLogger("http", cfg.Streams.HTTP, baseLogger),
|
||||
App: createLogger("app", cfg.Streams.App, baseLogger),
|
||||
}
|
||||
}
|
||||
|
||||
func NewSimpleLogger() *Logger {
|
||||
return NewLogger(config.LogConfig{
|
||||
Level: "info",
|
||||
Json: false,
|
||||
Streams: config.LogStreams{
|
||||
HTTP: config.LogStreamConfig{Enabled: true},
|
||||
App: config.LogStreamConfig{Enabled: true},
|
||||
Audit: config.LogStreamConfig{Enabled: false},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Logger) Init() {
|
||||
Audit = l.Audit
|
||||
HTTP = l.HTTP
|
||||
App = l.App
|
||||
}
|
||||
|
||||
func createLogger(component string, streamCfg config.LogStreamConfig, baseLogger zerolog.Logger) zerolog.Logger {
|
||||
if !streamCfg.Enabled {
|
||||
return zerolog.Nop()
|
||||
}
|
||||
subLogger := baseLogger.With().Str("log_stream", component).Logger()
|
||||
// override level if specified, otherwise use base level
|
||||
if streamCfg.Level != "" {
|
||||
subLogger = subLogger.Level(parseLogLevel(streamCfg.Level))
|
||||
}
|
||||
return subLogger
|
||||
}
|
||||
|
||||
func parseLogLevel(level string) zerolog.Level {
|
||||
if level == "" {
|
||||
return zerolog.InfoLevel
|
||||
}
|
||||
parsedLevel, err := zerolog.ParseLevel(strings.ToLower(level))
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Str("level", level).Msg("Invalid log level, defaulting to info")
|
||||
parsedLevel = zerolog.InfoLevel
|
||||
}
|
||||
return parsedLevel
|
||||
}
|
||||
93
internal/utils/tlog/log_wrapper_test.go
Normal file
93
internal/utils/tlog/log_wrapper_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package tlog_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/steveiliop56/tinyauth/internal/config"
|
||||
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestNewLogger(t *testing.T) {
|
||||
cfg := config.LogConfig{
|
||||
Level: "debug",
|
||||
Json: true,
|
||||
Streams: config.LogStreams{
|
||||
HTTP: config.LogStreamConfig{Enabled: true, Level: "info"},
|
||||
App: config.LogStreamConfig{Enabled: true, Level: ""},
|
||||
Audit: config.LogStreamConfig{Enabled: false, Level: ""},
|
||||
},
|
||||
}
|
||||
|
||||
logger := tlog.NewLogger(cfg)
|
||||
|
||||
assert.Assert(t, logger != nil)
|
||||
assert.Assert(t, logger.HTTP.GetLevel() == zerolog.InfoLevel)
|
||||
assert.Assert(t, logger.App.GetLevel() == zerolog.DebugLevel)
|
||||
assert.Assert(t, logger.Audit.GetLevel() == zerolog.Disabled)
|
||||
}
|
||||
|
||||
func TestNewSimpleLogger(t *testing.T) {
|
||||
logger := tlog.NewSimpleLogger()
|
||||
assert.Assert(t, logger != nil)
|
||||
assert.Assert(t, logger.HTTP.GetLevel() == zerolog.InfoLevel)
|
||||
assert.Assert(t, logger.App.GetLevel() == zerolog.InfoLevel)
|
||||
assert.Assert(t, logger.Audit.GetLevel() == zerolog.Disabled)
|
||||
}
|
||||
|
||||
func TestLoggerInit(t *testing.T) {
|
||||
logger := tlog.NewSimpleLogger()
|
||||
logger.Init()
|
||||
|
||||
assert.Assert(t, tlog.App.GetLevel() != zerolog.Disabled)
|
||||
}
|
||||
|
||||
func TestLoggerWithDisabledStreams(t *testing.T) {
|
||||
cfg := config.LogConfig{
|
||||
Level: "info",
|
||||
Json: false,
|
||||
Streams: config.LogStreams{
|
||||
HTTP: config.LogStreamConfig{Enabled: false},
|
||||
App: config.LogStreamConfig{Enabled: false},
|
||||
Audit: config.LogStreamConfig{Enabled: false},
|
||||
},
|
||||
}
|
||||
|
||||
logger := tlog.NewLogger(cfg)
|
||||
|
||||
assert.Assert(t, logger.HTTP.GetLevel() == zerolog.Disabled)
|
||||
assert.Assert(t, logger.App.GetLevel() == zerolog.Disabled)
|
||||
assert.Assert(t, logger.Audit.GetLevel() == zerolog.Disabled)
|
||||
}
|
||||
|
||||
func TestLogStreamField(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
cfg := config.LogConfig{
|
||||
Level: "info",
|
||||
Json: true,
|
||||
Streams: config.LogStreams{
|
||||
HTTP: config.LogStreamConfig{Enabled: true},
|
||||
App: config.LogStreamConfig{Enabled: true},
|
||||
Audit: config.LogStreamConfig{Enabled: true},
|
||||
},
|
||||
}
|
||||
|
||||
logger := tlog.NewLogger(cfg)
|
||||
|
||||
// Override output for HTTP logger to capture output
|
||||
logger.HTTP = logger.HTTP.Output(&buf)
|
||||
|
||||
logger.HTTP.Info().Msg("test message")
|
||||
|
||||
var logEntry map[string]interface{}
|
||||
err := json.Unmarshal(buf.Bytes(), &logEntry)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, "http", logEntry["log_stream"])
|
||||
assert.Equal(t, "test message", logEntry["message"])
|
||||
}
|
||||
Reference in New Issue
Block a user