(fix): token replacer fix
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.0.0-rc.16
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Bot loggers now apply the configured token replacer consistently across the main bot logger, request logger, internal API and uploader loggers, webhook logger, and auto-managed plugin loggers, so bot tokens stay masked in both stdout and file-backed logs.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
- Added regression coverage proving token masking still applies after `initLoggers(...)` switches loggers to file-backed writers and that auto-managed plugin loggers inherit token masking as well.
|
||||||
|
|
||||||
## v1.0.0-rc.15
|
## v1.0.0-rc.15
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ func NewBot[T any](opts *BotOpts) (*Bot[T], error) {
|
|||||||
bot.logger.Warn("Can't get bot username. Named command handlers won't work!")
|
bot.logger.Warn("Can't get bot username. Named command handlers won't work!")
|
||||||
}
|
}
|
||||||
bot.logger.Infoln(fmt.Sprintf("Authorized as %s (@%s)", u.FirstName, Val(u.Username, "unknown")))
|
bot.logger.Infoln(fmt.Sprintf("Authorized as %s (@%s)", u.FirstName, Val(u.Username, "unknown")))
|
||||||
|
bot.logger.Debugln("Bot initialized with configuration:", fmt.Sprintf("%+v", opts))
|
||||||
|
|
||||||
return bot, nil
|
return bot, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -30,6 +30,7 @@ func (bot *Bot[T]) AddPlugins(plugin ...*Plugin[T]) *Bot[T] {
|
|||||||
if cloned.logger == nil {
|
if cloned.logger == nil {
|
||||||
cloned.logger = utils.CreateLogger(cloned.name, level)
|
cloned.logger = utils.CreateLogger(cloned.name, level)
|
||||||
}
|
}
|
||||||
|
bot.addTokenReplacer(cloned.logger)
|
||||||
bot.plugins = append(bot.plugins, cloned)
|
bot.plugins = append(bot.plugins, cloned)
|
||||||
if bot.logger != nil {
|
if bot.logger != nil {
|
||||||
bot.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", cloned.name))
|
bot.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", cloned.name))
|
||||||
@@ -145,7 +146,7 @@ func (bot *Bot[T]) AddAppDataLoggerWriter(writer AppDataLogger[T]) *Bot[T] {
|
|||||||
if bot.RequestLogger != nil {
|
if bot.RequestLogger != nil {
|
||||||
bot.RequestLogger.AddWriter(w)
|
bot.RequestLogger.AddWriter(w)
|
||||||
}
|
}
|
||||||
for _, l := range bot.extraLoggers {
|
for _, l := range bot.managedExtraLoggers() {
|
||||||
l.AddWriter(w)
|
l.AddWriter(w)
|
||||||
}
|
}
|
||||||
for _, p := range bot.plugins {
|
for _, p := range bot.plugins {
|
||||||
@@ -153,5 +154,7 @@ func (bot *Bot[T]) AddAppDataLoggerWriter(writer AppDataLogger[T]) *Bot[T] {
|
|||||||
p.logger.AddWriter(w)
|
p.logger.AddWriter(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bot.addTokenReplacer(bot.logger, bot.RequestLogger)
|
||||||
|
bot.addTokenReplacer(bot.managedExtraLoggers()...)
|
||||||
return bot
|
return bot
|
||||||
}
|
}
|
||||||
|
|||||||
+119
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -136,6 +137,124 @@ func TestInitLoggersFallsBackToStdoutLoggerOnFileError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitLoggersAppliesTokenReplacerToFileLoggers(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
api := tgapi.NewAPI(tgapi.NewAPIOpts("secret-token"))
|
||||||
|
uploader := tgapi.NewUploader(api)
|
||||||
|
bot := &Bot[NoData]{token: "secret-token", api: api, uploader: uploader}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := uploader.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close uploader: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := api.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close api: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bot.initLoggers(&BotOpts{
|
||||||
|
Debug: true,
|
||||||
|
WriteToFile: true,
|
||||||
|
UseRequestLogger: true,
|
||||||
|
LoggerBasePath: tempDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
apiPath := filepath.Join(tempDir, "api.log")
|
||||||
|
apiFile, err := os.OpenFile(apiPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open api log: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = apiFile.Close() }()
|
||||||
|
bot.api.GetLogger().AddWriter(bot.api.GetLogger().CreateTextWriter(apiFile))
|
||||||
|
|
||||||
|
uploaderPath := filepath.Join(tempDir, "uploader.log")
|
||||||
|
uploaderFile, err := os.OpenFile(uploaderPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open uploader log: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = uploaderFile.Close() }()
|
||||||
|
bot.uploader.GetLogger().AddWriter(bot.uploader.GetLogger().CreateTextWriter(uploaderFile))
|
||||||
|
|
||||||
|
bot.logger.Infoln("main secret-token")
|
||||||
|
bot.RequestLogger.Infoln("request secret-token")
|
||||||
|
bot.api.GetLogger().Infoln("api secret-token")
|
||||||
|
bot.uploader.GetLogger().Infoln("uploader secret-token")
|
||||||
|
|
||||||
|
if err := bot.RequestLogger.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close request logger: %v", err)
|
||||||
|
}
|
||||||
|
if err := bot.logger.Close(); err != nil {
|
||||||
|
t.Fatalf("failed to close main logger: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainLog, err := os.ReadFile(filepath.Join(tempDir, "main.log"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read main log: %v", err)
|
||||||
|
}
|
||||||
|
requestLog, err := os.ReadFile(filepath.Join(tempDir, "requests.log"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read request log: %v", err)
|
||||||
|
}
|
||||||
|
apiLog, err := os.ReadFile(apiPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read api log: %v", err)
|
||||||
|
}
|
||||||
|
uploaderLog, err := os.ReadFile(uploaderPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read uploader log: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
}{
|
||||||
|
{name: "main", data: string(mainLog)},
|
||||||
|
{name: "request", data: string(requestLog)},
|
||||||
|
{name: "api", data: string(apiLog)},
|
||||||
|
{name: "uploader", data: string(uploaderLog)},
|
||||||
|
} {
|
||||||
|
if strings.Contains(tt.data, "secret-token") {
|
||||||
|
t.Fatalf("%s log leaked raw token: %q", tt.name, tt.data)
|
||||||
|
}
|
||||||
|
if !strings.Contains(tt.data, "<TOKEN>") {
|
||||||
|
t.Fatalf("%s log did not contain masked token: %q", tt.name, tt.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddPluginsAppliesTokenReplacerToPluginLogger(t *testing.T) {
|
||||||
|
bot := &Bot[NoData]{
|
||||||
|
token: "secret-token",
|
||||||
|
logger: slog.CreateLogger(),
|
||||||
|
}
|
||||||
|
defer func() { _ = bot.logger.Close() }()
|
||||||
|
|
||||||
|
plugin := NewPlugin[NoData]("demo")
|
||||||
|
bot.AddPlugins(plugin)
|
||||||
|
|
||||||
|
logPath := filepath.Join(t.TempDir(), "plugin.log")
|
||||||
|
file, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to open plugin log: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
bot.plugins[0].logger.AddWriter(bot.plugins[0].logger.CreateTextWriter(file))
|
||||||
|
bot.plugins[0].logger.Infoln("plugin secret-token")
|
||||||
|
|
||||||
|
data, err := os.ReadFile(logPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read plugin log: %v", err)
|
||||||
|
}
|
||||||
|
if strings.Contains(string(data), "secret-token") {
|
||||||
|
t.Fatalf("plugin log leaked raw token: %q", string(data))
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), "<TOKEN>") {
|
||||||
|
t.Fatalf("plugin log did not contain masked token: %q", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNextPollRetryDelay(t *testing.T) {
|
func TestNextPollRetryDelay(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
+39
-2
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,6 +16,39 @@ import (
|
|||||||
"github.com/alitto/pond/v2"
|
"github.com/alitto/pond/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (bot *Bot[T]) addTokenReplacer(loggers ...*slog.Logger) {
|
||||||
|
if bot.token == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, logger := range loggers {
|
||||||
|
if logger == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
logger.AddReplacer(bot.token, "<TOKEN>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUniqueLogger(loggers []*slog.Logger, logger *slog.Logger) []*slog.Logger {
|
||||||
|
if logger == nil {
|
||||||
|
return loggers
|
||||||
|
}
|
||||||
|
if slices.Contains(loggers, logger) {
|
||||||
|
return loggers
|
||||||
|
}
|
||||||
|
return append(loggers, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot[T]) managedExtraLoggers() []*slog.Logger {
|
||||||
|
loggers := append([]*slog.Logger(nil), bot.extraLoggers...)
|
||||||
|
if bot.api != nil {
|
||||||
|
loggers = appendUniqueLogger(loggers, bot.api.GetLogger())
|
||||||
|
}
|
||||||
|
if bot.uploader != nil {
|
||||||
|
loggers = appendUniqueLogger(loggers, bot.uploader.GetLogger())
|
||||||
|
}
|
||||||
|
return loggers
|
||||||
|
}
|
||||||
|
|
||||||
func (bot *Bot[T]) enqueueUpdate(ctx context.Context, update tgapi.Update) error {
|
func (bot *Bot[T]) enqueueUpdate(ctx context.Context, update tgapi.Update) error {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -41,7 +75,7 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
|
|||||||
level = slog.DEBUG
|
level = slog.DEBUG
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.logger = utils.CreateLogger("BOT", level).AddReplacer(bot.token, "<TOKEN>")
|
bot.logger = utils.CreateLogger("BOT", level)
|
||||||
if opts.WriteToFile {
|
if opts.WriteToFile {
|
||||||
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
|
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
|
||||||
logger, err := utils.CreateFileLogger("BOT", level, path)
|
logger, err := utils.CreateFileLogger("BOT", level, path)
|
||||||
@@ -53,7 +87,7 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.UseRequestLogger {
|
if opts.UseRequestLogger {
|
||||||
bot.RequestLogger = utils.CreateLogger("REQUESTS", level).AddReplacer(bot.token, "<TOKEN>")
|
bot.RequestLogger = utils.CreateLogger("REQUESTS", level)
|
||||||
if opts.WriteToFile {
|
if opts.WriteToFile {
|
||||||
path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/"))
|
path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/"))
|
||||||
logger, err := utils.CreateFileLogger("REQUESTS", level, path)
|
logger, err := utils.CreateFileLogger("REQUESTS", level, path)
|
||||||
@@ -64,6 +98,9 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bot.addTokenReplacer(bot.logger, bot.RequestLogger)
|
||||||
|
bot.addTokenReplacer(bot.managedExtraLoggers()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bot *Bot[T]) beginRun() error {
|
func (bot *Bot[T]) beginRun() error {
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ func (bot *Bot[T]) RunWebHookWithContext(ctx context.Context, opts *BotWebHookOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
bot.webHookLogger = utils.CreateLogger("WEBHOOK", bot.GetLoggerLevel())
|
bot.webHookLogger = utils.CreateLogger("WEBHOOK", bot.GetLoggerLevel())
|
||||||
|
bot.addTokenReplacer(bot.webHookLogger)
|
||||||
if opts.SecretToken == "" {
|
if opts.SecretToken == "" {
|
||||||
bot.webHookLogger.Warnln("Bot webhook secret token empty. It's VERY recommended to set secret.")
|
bot.webHookLogger.Warnln("Bot webhook secret token empty. It's VERY recommended to set secret.")
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ package utils
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VersionString is the module version string.
|
// VersionString is the module version string.
|
||||||
VersionString = "1.0.0-rc.15"
|
VersionString = "1.0.0-rc.16"
|
||||||
// VersionMajor is the module major version.
|
// VersionMajor is the module major version.
|
||||||
VersionMajor = 1
|
VersionMajor = 1
|
||||||
// VersionMinor is the module minor version.
|
// VersionMinor is the module minor version.
|
||||||
@@ -10,5 +10,5 @@ const (
|
|||||||
// VersionPatch is the module patch version.
|
// VersionPatch is the module patch version.
|
||||||
VersionPatch = 0
|
VersionPatch = 0
|
||||||
// VersionBeta is the prerelease counter for the current version.
|
// VersionBeta is the prerelease counter for the current version.
|
||||||
VersionBeta = 15
|
VersionBeta = 16
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user