fix: address review findings — dynamic version, deduplicate KILL, update README
All checks were successful
check / check (push) Successful in 1m3s
All checks were successful
check / check (push) Successful in 1m3s
This commit is contained in:
@@ -2307,8 +2307,8 @@ IRC_LISTEN_ADDR=
|
|||||||
| Connection | `NICK`, `USER`, `PASS`, `QUIT`, `PING`/`PONG`, `CAP` |
|
| Connection | `NICK`, `USER`, `PASS`, `QUIT`, `PING`/`PONG`, `CAP` |
|
||||||
| Channels | `JOIN`, `PART`, `MODE`, `TOPIC`, `NAMES`, `LIST`, `KICK`, `INVITE` |
|
| Channels | `JOIN`, `PART`, `MODE`, `TOPIC`, `NAMES`, `LIST`, `KICK`, `INVITE` |
|
||||||
| Messaging | `PRIVMSG`, `NOTICE` |
|
| Messaging | `PRIVMSG`, `NOTICE` |
|
||||||
| Info | `WHO`, `WHOIS`, `LUSERS`, `MOTD`, `AWAY` |
|
| Info | `WHO`, `WHOIS`, `LUSERS`, `MOTD`, `AWAY`, `USERHOST`, `VERSION`, `ADMIN`, `INFO`, `TIME` |
|
||||||
| Operator | `OPER` (requires `NEOIRC_OPER_NAME` and `NEOIRC_OPER_PASSWORD`) |
|
| Operator | `OPER`, `KILL`, `WALLOPS` (requires `NEOIRC_OPER_NAME` and `NEOIRC_OPER_PASSWORD`) |
|
||||||
|
|
||||||
### Protocol Details
|
### Protocol Details
|
||||||
|
|
||||||
@@ -2820,6 +2820,10 @@ guess is borne by the server (bcrypt), not the client.
|
|||||||
login from additional devices via `POST /api/v1/login`
|
login from additional devices via `POST /api/v1/login`
|
||||||
- [x] **Cookie-based auth** — HttpOnly cookies replace Bearer tokens for
|
- [x] **Cookie-based auth** — HttpOnly cookies replace Bearer tokens for
|
||||||
all API authentication
|
all API authentication
|
||||||
|
- [x] **Tier 3 utility commands** — USERHOST (302), VERSION (351), ADMIN
|
||||||
|
(256–259), INFO (371/374), TIME (391), KILL (oper-only forced
|
||||||
|
disconnect), WALLOPS (oper-only broadcast to +w users)
|
||||||
|
- [x] **User mode +w** — wallops usermode via `MODE nick +w/-w`
|
||||||
|
|
||||||
### Future (1.0+)
|
### Future (1.0+)
|
||||||
|
|
||||||
|
|||||||
@@ -331,18 +331,12 @@ func (hdlr *Handlers) handleKill(
|
|||||||
}
|
}
|
||||||
|
|
||||||
lines := bodyLines()
|
lines := bodyLines()
|
||||||
if len(lines) == 0 {
|
|
||||||
hdlr.respondIRCError(
|
|
||||||
writer, request, clientID, sessionID,
|
|
||||||
irc.ErrNeedMoreParams, nick,
|
|
||||||
[]string{irc.CmdKill},
|
|
||||||
"Not enough parameters",
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
var targetNick string
|
||||||
|
if len(lines) > 0 {
|
||||||
|
targetNick = strings.TrimSpace(lines[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
targetNick := strings.TrimSpace(lines[0])
|
|
||||||
if targetNick == "" {
|
if targetNick == "" {
|
||||||
hdlr.respondIRCError(
|
hdlr.respondIRCError(
|
||||||
writer, request, clientID, sessionID,
|
writer, request, clientID, sessionID,
|
||||||
@@ -383,8 +377,11 @@ func (hdlr *Handlers) handleKill(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hdlr.executeKillUser(
|
quitReason := "Killed (" + nick + " (" + reason + "))"
|
||||||
request, targetSID, targetNick, nick, reason,
|
|
||||||
|
hdlr.svc.BroadcastQuit(
|
||||||
|
request.Context(), targetSID,
|
||||||
|
targetNick, quitReason,
|
||||||
)
|
)
|
||||||
|
|
||||||
hdlr.respondJSON(writer, request,
|
hdlr.respondJSON(writer, request,
|
||||||
@@ -392,71 +389,6 @@ func (hdlr *Handlers) handleKill(
|
|||||||
http.StatusOK)
|
http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// executeKillUser forcibly disconnects a user: broadcasts
|
|
||||||
// QUIT to their channels, parts all channels, and deletes
|
|
||||||
// the session.
|
|
||||||
func (hdlr *Handlers) executeKillUser(
|
|
||||||
request *http.Request,
|
|
||||||
targetSID int64,
|
|
||||||
targetNick, killerNick, reason string,
|
|
||||||
) {
|
|
||||||
ctx := request.Context()
|
|
||||||
|
|
||||||
quitMsg := "Killed (" + killerNick + " (" + reason + "))"
|
|
||||||
|
|
||||||
quitBody, err := json.Marshal([]string{quitMsg})
|
|
||||||
if err != nil {
|
|
||||||
hdlr.log.Error(
|
|
||||||
"marshal kill quit body", "error", err,
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
channels, _ := hdlr.params.Database.
|
|
||||||
GetSessionChannels(ctx, targetSID)
|
|
||||||
|
|
||||||
notified := map[int64]bool{}
|
|
||||||
|
|
||||||
var dbID int64
|
|
||||||
|
|
||||||
if len(channels) > 0 {
|
|
||||||
dbID, _, _ = hdlr.params.Database.InsertMessage(
|
|
||||||
ctx, irc.CmdQuit, targetNick, "",
|
|
||||||
nil, json.RawMessage(quitBody), nil,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, chanInfo := range channels {
|
|
||||||
memberIDs, _ := hdlr.params.Database.
|
|
||||||
GetChannelMemberIDs(ctx, chanInfo.ID)
|
|
||||||
|
|
||||||
for _, mid := range memberIDs {
|
|
||||||
if mid != targetSID && !notified[mid] {
|
|
||||||
notified[mid] = true
|
|
||||||
|
|
||||||
_ = hdlr.params.Database.EnqueueToSession(
|
|
||||||
ctx, mid, dbID,
|
|
||||||
)
|
|
||||||
|
|
||||||
hdlr.broker.Notify(mid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = hdlr.params.Database.PartChannel(
|
|
||||||
ctx, chanInfo.ID, targetSID,
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = hdlr.params.Database.DeleteChannelIfEmpty(
|
|
||||||
ctx, chanInfo.ID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = hdlr.params.Database.DeleteSession(
|
|
||||||
ctx, targetSID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleWallops handles the WALLOPS command.
|
// handleWallops handles the WALLOPS command.
|
||||||
// Broadcasts a message to all users with +w usermode
|
// Broadcasts a message to all users with +w usermode
|
||||||
// (oper only).
|
// (oper only).
|
||||||
|
|||||||
@@ -8,10 +8,29 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"sneak.berlin/go/neoirc/internal/globals"
|
||||||
"sneak.berlin/go/neoirc/internal/service"
|
"sneak.berlin/go/neoirc/internal/service"
|
||||||
"sneak.berlin/go/neoirc/pkg/irc"
|
"sneak.berlin/go/neoirc/pkg/irc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// versionString returns the server version for IRC
|
||||||
|
// responses, falling back to "neoirc-dev" when globals
|
||||||
|
// are not set (e.g. during tests).
|
||||||
|
func versionString() string {
|
||||||
|
name := globals.Appname
|
||||||
|
ver := globals.Version
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = "neoirc"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ver == "" {
|
||||||
|
ver = "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
return name + "-" + ver
|
||||||
|
}
|
||||||
|
|
||||||
// sendIRCError maps a service.IRCError to an IRC numeric
|
// sendIRCError maps a service.IRCError to an IRC numeric
|
||||||
// reply on the wire.
|
// reply on the wire.
|
||||||
func (c *Conn) sendIRCError(err error) {
|
func (c *Conn) sendIRCError(err error) {
|
||||||
@@ -1384,7 +1403,7 @@ func (c *Conn) handleUserhost(
|
|||||||
func (c *Conn) handleVersion(ctx context.Context) {
|
func (c *Conn) handleVersion(ctx context.Context) {
|
||||||
_ = ctx
|
_ = ctx
|
||||||
|
|
||||||
version := "neoirc-0.1"
|
version := versionString()
|
||||||
|
|
||||||
c.sendNumeric(
|
c.sendNumeric(
|
||||||
irc.RplVersion,
|
irc.RplVersion,
|
||||||
@@ -1426,7 +1445,7 @@ func (c *Conn) handleInfo(ctx context.Context) {
|
|||||||
|
|
||||||
infoLines := []string{
|
infoLines := []string{
|
||||||
"neoirc — IRC semantics over HTTP",
|
"neoirc — IRC semantics over HTTP",
|
||||||
"Version: neoirc-0.1",
|
"Version: " + versionString(),
|
||||||
"Written in Go",
|
"Written in Go",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user