mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-10-03 05:12:52 +00:00
Phase 4 of the 2026-05-09 review remediation plan. - internal/log: thin facade over log/slog.NewJSONHandler writing to stdout. Cloud Run's Cloud Logging integration auto-parses level, time, msg fields. Honours LOG_LEVEL env (debug|info|warn|error). Re-exports Info/Warn/Error/Fatal/Debug/With ergonomics. - Migrated all 22 stdlib log call sites: cmd/server/main.go (17), internal/server/router.go (2), internal/modules/dispatcher.go (1), internal/telegram/webhook.go (1), internal/modules/misc/misc.go (1). Format-string args replaced with structured key/value attrs. - Closes log-injection class (J3 from security audit) — slog escapes newlines and quotes inside field values, so attacker-controlled strings cannot synthesise fake log records (test: TestNewlineEscaping_NoLogInjection). go test -race -count=1 ./... clean across all 13 packages. Zero stdlib log imports remain outside internal/log.
69 lines
2.2 KiB
Go
69 lines
2.2 KiB
Go
package modules
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/go-telegram/bot"
|
|
"github.com/go-telegram/bot/models"
|
|
|
|
"github.com/tiennm99/miti99bot-go/internal/log"
|
|
)
|
|
|
|
// Auth gates Protected/Private commands by sender Telegram user ID. Public
|
|
// commands are always allowed. A zero BotOwnerID + empty AdminUserIDs means
|
|
// every Protected/Private command is denied — the safe default for an
|
|
// unconfigured deployment.
|
|
type Auth struct {
|
|
BotOwnerID int64 // owner is implicitly an admin; receives Private + Protected
|
|
AdminUserIDs map[int64]bool // additional users allowed to run Protected commands
|
|
}
|
|
|
|
// Permits reports whether the sender of update may run a command of visibility v.
|
|
// Denies are silent — callers must NOT reply to denied requests, otherwise the
|
|
// existence of a Protected/Private command is leaked to unprivileged users.
|
|
func (a Auth) Permits(v Visibility, update *models.Update) bool {
|
|
if v == VisibilityPublic {
|
|
return true
|
|
}
|
|
if update == nil || update.Message == nil || update.Message.From == nil {
|
|
return false
|
|
}
|
|
senderID := update.Message.From.ID
|
|
switch v {
|
|
case VisibilityPrivate:
|
|
return a.BotOwnerID != 0 && senderID == a.BotOwnerID
|
|
case VisibilityProtected:
|
|
if a.BotOwnerID != 0 && senderID == a.BotOwnerID {
|
|
return true
|
|
}
|
|
return a.AdminUserIDs[senderID]
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Install registers every command in the registry with the Telegram bot.
|
|
//
|
|
// MatchTypeCommand expects the bare command name without the leading slash;
|
|
// the library compares against entity bytes after the "/" prefix.
|
|
//
|
|
// auth gates Protected/Private commands; pass a zero-value Auth to deny all
|
|
// Protected/Private commands (the right answer for a misconfigured deploy).
|
|
func Install(b *bot.Bot, reg *Registry, auth Auth) {
|
|
for name, cmd := range reg.AllCommands {
|
|
cmdCopy := cmd // capture by value for the closure
|
|
b.RegisterHandler(
|
|
bot.HandlerTypeMessageText,
|
|
name,
|
|
bot.MatchTypeCommand,
|
|
func(ctx context.Context, b *bot.Bot, update *models.Update) {
|
|
if !auth.Permits(cmdCopy.Visibility, update) {
|
|
return // silent — do not leak existence of gated commands
|
|
}
|
|
if err := cmdCopy.Handler(ctx, b, update); err != nil {
|
|
log.Error("command failed", "command", cmdCopy.Name, "err", err)
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|