All checks were successful
check / check (push) Successful in 58s
IRC commands (PRIVMSG, JOIN, PART, NICK, TOPIC, etc.) now respond with proper IRC numeric replies delivered through the message queue instead of HTTP status codes. HTTP error codes are now reserved exclusively for transport-level concerns: auth failures (401), malformed requests (400), and server errors (500). Changes: - Add params column to messages table for IRC-style parameters - Add Params field to IRCMessage struct and update all queries - Add respondIRCError helper for consistent IRC error delivery - Add RPL_WELCOME (001) on session creation and login - Add RPL_TOPIC/RPL_NOTOPIC (332/331), RPL_NAMREPLY (353), RPL_ENDOFNAMES (366) on JOIN - Add RPL_TOPIC (332) on TOPIC set - Replace HTTP 404 with ERR_NOSUCHCHANNEL (403) and ERR_NOSUCHNICK (401) - Replace HTTP 409 with ERR_NICKNAMEINUSE (433) - Replace HTTP 403 with ERR_NOTONCHANNEL (442) - Replace HTTP 400 with ERR_NEEDMOREPARAMS (461), ERR_ERRONEUSNICKNAME (432), and ERR_UNKNOWNCOMMAND (421) where appropriate - Change PRIVMSG/NOTICE success from HTTP 201 to HTTP 200 - Update all tests to verify IRC numerics in message queue - Add new tests for RPL_WELCOME and JOIN numerics - Update README to document new numeric reply behavior closes #54
191 lines
3.4 KiB
Go
191 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
const minPasswordLength = 8
|
|
|
|
// HandleRegister creates a new user with a password.
|
|
func (hdlr *Handlers) HandleRegister() http.HandlerFunc {
|
|
return func(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
request.Body = http.MaxBytesReader(
|
|
writer, request.Body, hdlr.maxBodySize(),
|
|
)
|
|
|
|
hdlr.handleRegister(writer, request)
|
|
}
|
|
}
|
|
|
|
func (hdlr *Handlers) handleRegister(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
type registerRequest struct {
|
|
Nick string `json:"nick"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
var payload registerRequest
|
|
|
|
err := json.NewDecoder(request.Body).Decode(&payload)
|
|
if err != nil {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"invalid request body",
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
payload.Nick = strings.TrimSpace(payload.Nick)
|
|
|
|
if !validNickRe.MatchString(payload.Nick) {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"invalid nick format",
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
if len(payload.Password) < minPasswordLength {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"password must be at least 8 characters",
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
sessionID, clientID, token, err :=
|
|
hdlr.params.Database.RegisterUser(
|
|
request.Context(),
|
|
payload.Nick,
|
|
payload.Password,
|
|
)
|
|
if err != nil {
|
|
hdlr.handleRegisterError(
|
|
writer, request, err,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
hdlr.deliverMOTD(request, clientID, sessionID, payload.Nick)
|
|
|
|
hdlr.respondJSON(writer, request, map[string]any{
|
|
"id": sessionID,
|
|
"nick": payload.Nick,
|
|
"token": token,
|
|
}, http.StatusCreated)
|
|
}
|
|
|
|
func (hdlr *Handlers) handleRegisterError(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
err error,
|
|
) {
|
|
if strings.Contains(err.Error(), "UNIQUE") {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"nick already taken",
|
|
http.StatusConflict,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
hdlr.log.Error(
|
|
"register user failed", "error", err,
|
|
)
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"internal error",
|
|
http.StatusInternalServerError,
|
|
)
|
|
}
|
|
|
|
// HandleLogin authenticates a user with nick and password.
|
|
func (hdlr *Handlers) HandleLogin() http.HandlerFunc {
|
|
return func(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
request.Body = http.MaxBytesReader(
|
|
writer, request.Body, hdlr.maxBodySize(),
|
|
)
|
|
|
|
hdlr.handleLogin(writer, request)
|
|
}
|
|
}
|
|
|
|
func (hdlr *Handlers) handleLogin(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
type loginRequest struct {
|
|
Nick string `json:"nick"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
var payload loginRequest
|
|
|
|
err := json.NewDecoder(request.Body).Decode(&payload)
|
|
if err != nil {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"invalid request body",
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
payload.Nick = strings.TrimSpace(payload.Nick)
|
|
|
|
if payload.Nick == "" || payload.Password == "" {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"nick and password required",
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
sessionID, clientID, token, err :=
|
|
hdlr.params.Database.LoginUser(
|
|
request.Context(),
|
|
payload.Nick,
|
|
payload.Password,
|
|
)
|
|
if err != nil {
|
|
hdlr.respondError(
|
|
writer, request,
|
|
"invalid credentials",
|
|
http.StatusUnauthorized,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
hdlr.deliverMOTD(
|
|
request, clientID, sessionID, payload.Nick,
|
|
)
|
|
|
|
hdlr.respondJSON(writer, request, map[string]any{
|
|
"id": sessionID,
|
|
"nick": payload.Nick,
|
|
"token": token,
|
|
}, http.StatusOK)
|
|
}
|