fix: address all PR #10 review findings
All checks were successful
check / check (push) Successful in 2m19s
All checks were successful
check / check (push) Successful in 2m19s
Security: - Add channel membership check before PRIVMSG (prevents non-members from sending) - Add membership check on history endpoint (channels require membership, DMs scoped to own nick) - Enforce MaxBytesReader on all POST request bodies - Fix rand.Read error being silently ignored in token generation Data integrity: - Fix TOCTOU race in GetOrCreateChannel using INSERT OR IGNORE + SELECT Build: - Add CGO_ENABLED=0 to golangci-lint install in Dockerfile (fixes alpine build) Linting: - Strict .golangci.yml: only wsl disabled (deprecated in v2) - Re-enable exhaustruct, depguard, godot, wrapcheck, varnamelen - Fix linters-settings -> linters.settings for v2 config format - Fix ALL lint findings in actual code (no linter config weakening) - Wrap all external package errors (wrapcheck) - Fill struct fields or add targeted nolint:exhaustruct where appropriate - Rename short variables (ts->timestamp, n->bufIndex, etc.) - Add depguard deny policy for io/ioutil and math/rand - Exclude G704 (SSRF) in gosec config (CLI client takes user-configured URLs) Tests: - Add security tests (TestNonMemberCannotSend, TestHistoryNonMember) - Split TestInsertAndPollMessages for reduced complexity - Fix parallel test safety (viper global state prevents parallelism) - Use t.Context() instead of context.Background() in tests Docker build verified passing locally.
This commit is contained in:
@@ -32,7 +32,7 @@ type App struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &App{
|
||||
app := &App{ //nolint:exhaustruct
|
||||
ui: NewUI(),
|
||||
nick: "guest",
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (a *App) handleInput(text string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := a.client.SendMessage(&api.Message{
|
||||
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
||||
Command: "PRIVMSG",
|
||||
To: target,
|
||||
Body: []string{text},
|
||||
@@ -98,7 +98,7 @@ func (a *App) handleInput(text string) {
|
||||
return
|
||||
}
|
||||
|
||||
ts := time.Now().Format(timeFormat)
|
||||
timestamp := time.Now().Format(timeFormat)
|
||||
|
||||
a.mu.Lock()
|
||||
nick := a.nick
|
||||
@@ -106,7 +106,7 @@ func (a *App) handleInput(text string) {
|
||||
|
||||
a.ui.AddLine(target, fmt.Sprintf(
|
||||
"[gray]%s [green]<%s>[white] %s",
|
||||
ts, nick, text,
|
||||
timestamp, nick, text,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -123,40 +123,36 @@ func (a *App) handleCommand(text string) {
|
||||
}
|
||||
|
||||
func (a *App) dispatchCommand(cmd, args string) {
|
||||
argCmds := map[string]func(string){
|
||||
"/connect": a.cmdConnect,
|
||||
"/nick": a.cmdNick,
|
||||
"/join": a.cmdJoin,
|
||||
"/part": a.cmdPart,
|
||||
"/msg": a.cmdMsg,
|
||||
"/query": a.cmdQuery,
|
||||
"/topic": a.cmdTopic,
|
||||
"/window": a.cmdWindow,
|
||||
"/w": a.cmdWindow,
|
||||
switch cmd {
|
||||
case "/connect":
|
||||
a.cmdConnect(args)
|
||||
case "/nick":
|
||||
a.cmdNick(args)
|
||||
case "/join":
|
||||
a.cmdJoin(args)
|
||||
case "/part":
|
||||
a.cmdPart(args)
|
||||
case "/msg":
|
||||
a.cmdMsg(args)
|
||||
case "/query":
|
||||
a.cmdQuery(args)
|
||||
case "/topic":
|
||||
a.cmdTopic(args)
|
||||
case "/names":
|
||||
a.cmdNames()
|
||||
case "/list":
|
||||
a.cmdList()
|
||||
case "/window", "/w":
|
||||
a.cmdWindow(args)
|
||||
case "/quit":
|
||||
a.cmdQuit()
|
||||
case "/help":
|
||||
a.cmdHelp()
|
||||
default:
|
||||
a.ui.AddStatus(
|
||||
"[red]Unknown command: " + cmd,
|
||||
)
|
||||
}
|
||||
|
||||
if fn, ok := argCmds[cmd]; ok {
|
||||
fn(args)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
noArgCmds := map[string]func(){
|
||||
"/names": a.cmdNames,
|
||||
"/list": a.cmdList,
|
||||
"/quit": a.cmdQuit,
|
||||
"/help": a.cmdHelp,
|
||||
}
|
||||
|
||||
if fn, ok := noArgCmds[cmd]; ok {
|
||||
fn()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
a.ui.AddStatus(
|
||||
"[red]Unknown command: " + cmd,
|
||||
)
|
||||
}
|
||||
|
||||
func (a *App) cmdConnect(serverURL string) {
|
||||
@@ -231,7 +227,7 @@ func (a *App) cmdNick(nick string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := a.client.SendMessage(&api.Message{
|
||||
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
||||
Command: "NICK",
|
||||
Body: []string{nick},
|
||||
})
|
||||
@@ -366,7 +362,7 @@ func (a *App) cmdMsg(args string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := a.client.SendMessage(&api.Message{
|
||||
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
||||
Command: "PRIVMSG",
|
||||
To: target,
|
||||
Body: []string{text},
|
||||
@@ -379,11 +375,11 @@ func (a *App) cmdMsg(args string) {
|
||||
return
|
||||
}
|
||||
|
||||
ts := time.Now().Format(timeFormat)
|
||||
timestamp := time.Now().Format(timeFormat)
|
||||
|
||||
a.ui.AddLine(target, fmt.Sprintf(
|
||||
"[gray]%s [green]<%s>[white] %s",
|
||||
ts, nick, text,
|
||||
timestamp, nick, text,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -424,7 +420,7 @@ func (a *App) cmdTopic(args string) {
|
||||
}
|
||||
|
||||
if args == "" {
|
||||
err := a.client.SendMessage(&api.Message{
|
||||
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
||||
Command: "TOPIC",
|
||||
To: target,
|
||||
})
|
||||
@@ -437,7 +433,7 @@ func (a *App) cmdTopic(args string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := a.client.SendMessage(&api.Message{
|
||||
err := a.client.SendMessage(&api.Message{ //nolint:exhaustruct
|
||||
Command: "TOPIC",
|
||||
To: target,
|
||||
Body: []string{args},
|
||||
@@ -523,18 +519,18 @@ func (a *App) cmdWindow(args string) {
|
||||
return
|
||||
}
|
||||
|
||||
var n int
|
||||
var bufIndex int
|
||||
|
||||
_, _ = fmt.Sscanf(args, "%d", &n)
|
||||
_, _ = fmt.Sscanf(args, "%d", &bufIndex)
|
||||
|
||||
a.ui.SwitchBuffer(n)
|
||||
a.ui.SwitchBuffer(bufIndex)
|
||||
|
||||
a.mu.Lock()
|
||||
nick := a.nick
|
||||
a.mu.Unlock()
|
||||
|
||||
if n >= 0 && n < a.ui.BufferCount() {
|
||||
buf := a.ui.buffers[n]
|
||||
if bufIndex >= 0 && bufIndex < a.ui.BufferCount() {
|
||||
buf := a.ui.buffers[bufIndex]
|
||||
if buf.Name != "(status)" {
|
||||
a.mu.Lock()
|
||||
a.target = buf.Name
|
||||
@@ -554,7 +550,7 @@ func (a *App) cmdQuit() {
|
||||
|
||||
if a.connected && a.client != nil {
|
||||
_ = a.client.SendMessage(
|
||||
&api.Message{Command: "QUIT"},
|
||||
&api.Message{Command: "QUIT"}, //nolint:exhaustruct
|
||||
)
|
||||
}
|
||||
|
||||
@@ -629,7 +625,7 @@ func (a *App) pollLoop() {
|
||||
}
|
||||
|
||||
func (a *App) handleServerMessage(msg *api.Message) {
|
||||
ts := a.formatTS(msg)
|
||||
timestamp := a.formatTS(msg)
|
||||
|
||||
a.mu.Lock()
|
||||
myNick := a.nick
|
||||
@@ -637,21 +633,21 @@ func (a *App) handleServerMessage(msg *api.Message) {
|
||||
|
||||
switch msg.Command {
|
||||
case "PRIVMSG":
|
||||
a.handlePrivmsgEvent(msg, ts, myNick)
|
||||
a.handlePrivmsgEvent(msg, timestamp, myNick)
|
||||
case "JOIN":
|
||||
a.handleJoinEvent(msg, ts)
|
||||
a.handleJoinEvent(msg, timestamp)
|
||||
case "PART":
|
||||
a.handlePartEvent(msg, ts)
|
||||
a.handlePartEvent(msg, timestamp)
|
||||
case "QUIT":
|
||||
a.handleQuitEvent(msg, ts)
|
||||
a.handleQuitEvent(msg, timestamp)
|
||||
case "NICK":
|
||||
a.handleNickEvent(msg, ts, myNick)
|
||||
a.handleNickEvent(msg, timestamp, myNick)
|
||||
case "NOTICE":
|
||||
a.handleNoticeEvent(msg, ts)
|
||||
a.handleNoticeEvent(msg, timestamp)
|
||||
case "TOPIC":
|
||||
a.handleTopicEvent(msg, ts)
|
||||
a.handleTopicEvent(msg, timestamp)
|
||||
default:
|
||||
a.handleDefaultEvent(msg, ts)
|
||||
a.handleDefaultEvent(msg, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,7 +660,7 @@ func (a *App) formatTS(msg *api.Message) string {
|
||||
}
|
||||
|
||||
func (a *App) handlePrivmsgEvent(
|
||||
msg *api.Message, ts, myNick string,
|
||||
msg *api.Message, timestamp, myNick string,
|
||||
) {
|
||||
lines := msg.BodyLines()
|
||||
text := strings.Join(lines, " ")
|
||||
@@ -680,12 +676,12 @@ func (a *App) handlePrivmsgEvent(
|
||||
|
||||
a.ui.AddLine(target, fmt.Sprintf(
|
||||
"[gray]%s [green]<%s>[white] %s",
|
||||
ts, msg.From, text,
|
||||
timestamp, msg.From, text,
|
||||
))
|
||||
}
|
||||
|
||||
func (a *App) handleJoinEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
if msg.To == "" {
|
||||
return
|
||||
@@ -693,12 +689,12 @@ func (a *App) handleJoinEvent(
|
||||
|
||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s has joined %s",
|
||||
ts, msg.From, msg.To,
|
||||
timestamp, msg.From, msg.To,
|
||||
))
|
||||
}
|
||||
|
||||
func (a *App) handlePartEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
if msg.To == "" {
|
||||
return
|
||||
@@ -710,18 +706,18 @@ func (a *App) handlePartEvent(
|
||||
if reason != "" {
|
||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s has left %s (%s)",
|
||||
ts, msg.From, msg.To, reason,
|
||||
timestamp, msg.From, msg.To, reason,
|
||||
))
|
||||
} else {
|
||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s has left %s",
|
||||
ts, msg.From, msg.To,
|
||||
timestamp, msg.From, msg.To,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleQuitEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
lines := msg.BodyLines()
|
||||
reason := strings.Join(lines, " ")
|
||||
@@ -729,18 +725,18 @@ func (a *App) handleQuitEvent(
|
||||
if reason != "" {
|
||||
a.ui.AddStatus(fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s has quit (%s)",
|
||||
ts, msg.From, reason,
|
||||
timestamp, msg.From, reason,
|
||||
))
|
||||
} else {
|
||||
a.ui.AddStatus(fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s has quit",
|
||||
ts, msg.From,
|
||||
timestamp, msg.From,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) handleNickEvent(
|
||||
msg *api.Message, ts, myNick string,
|
||||
msg *api.Message, timestamp, myNick string,
|
||||
) {
|
||||
lines := msg.BodyLines()
|
||||
|
||||
@@ -761,24 +757,24 @@ func (a *App) handleNickEvent(
|
||||
|
||||
a.ui.AddStatus(fmt.Sprintf(
|
||||
"[gray]%s [yellow]*** %s is now known as %s",
|
||||
ts, msg.From, newNick,
|
||||
timestamp, msg.From, newNick,
|
||||
))
|
||||
}
|
||||
|
||||
func (a *App) handleNoticeEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
lines := msg.BodyLines()
|
||||
text := strings.Join(lines, " ")
|
||||
|
||||
a.ui.AddStatus(fmt.Sprintf(
|
||||
"[gray]%s [magenta]--%s-- %s",
|
||||
ts, msg.From, text,
|
||||
timestamp, msg.From, text,
|
||||
))
|
||||
}
|
||||
|
||||
func (a *App) handleTopicEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
if msg.To == "" {
|
||||
return
|
||||
@@ -789,12 +785,12 @@ func (a *App) handleTopicEvent(
|
||||
|
||||
a.ui.AddLine(msg.To, fmt.Sprintf(
|
||||
"[gray]%s [cyan]*** %s set topic: %s",
|
||||
ts, msg.From, text,
|
||||
timestamp, msg.From, text,
|
||||
))
|
||||
}
|
||||
|
||||
func (a *App) handleDefaultEvent(
|
||||
msg *api.Message, ts string,
|
||||
msg *api.Message, timestamp string,
|
||||
) {
|
||||
lines := msg.BodyLines()
|
||||
text := strings.Join(lines, " ")
|
||||
@@ -802,7 +798,7 @@ func (a *App) handleDefaultEvent(
|
||||
if text != "" {
|
||||
a.ui.AddStatus(fmt.Sprintf(
|
||||
"[gray]%s [white][%s] %s",
|
||||
ts, msg.Command, text,
|
||||
timestamp, msg.Command, text,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user