Compare commits
1 Commits
92d5145ac6
...
260f798af4
| Author | SHA1 | Date | |
|---|---|---|---|
| 260f798af4 |
@@ -1528,87 +1528,6 @@ func (hdlr *Handlers) executeJoin(
|
|||||||
http.StatusOK)
|
http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkJoinAllowed runs Tier 2 restrictions for an
|
|
||||||
// existing channel. Returns true if join is allowed.
|
|
||||||
func (hdlr *Handlers) checkJoinAllowed(
|
|
||||||
writer http.ResponseWriter,
|
|
||||||
request *http.Request,
|
|
||||||
sessionID, clientID int64,
|
|
||||||
nick, channel string,
|
|
||||||
chID int64,
|
|
||||||
suppliedKey string,
|
|
||||||
) bool {
|
|
||||||
ctx := request.Context()
|
|
||||||
|
|
||||||
// 1. Ban check — prevents banned users from joining.
|
|
||||||
isBanned, banErr := hdlr.params.Database.
|
|
||||||
IsSessionBanned(ctx, chID, sessionID)
|
|
||||||
if banErr == nil && isBanned {
|
|
||||||
hdlr.respondIRCError(
|
|
||||||
writer, request, clientID, sessionID,
|
|
||||||
irc.ErrBannedFromChan, nick,
|
|
||||||
[]string{channel},
|
|
||||||
"Cannot join channel (+b)",
|
|
||||||
)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Invite-only check (+i).
|
|
||||||
isInviteOnly, ioErr := hdlr.params.Database.
|
|
||||||
IsChannelInviteOnly(ctx, chID)
|
|
||||||
if ioErr == nil && isInviteOnly {
|
|
||||||
hasInvite, invErr := hdlr.params.Database.
|
|
||||||
HasChannelInvite(ctx, chID, sessionID)
|
|
||||||
if invErr != nil || !hasInvite {
|
|
||||||
hdlr.respondIRCError(
|
|
||||||
writer, request, clientID, sessionID,
|
|
||||||
irc.ErrInviteOnlyChan, nick,
|
|
||||||
[]string{channel},
|
|
||||||
"Cannot join channel (+i)",
|
|
||||||
)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Channel key check (+k).
|
|
||||||
key, keyErr := hdlr.params.Database.
|
|
||||||
GetChannelKey(ctx, chID)
|
|
||||||
if keyErr == nil && key != "" {
|
|
||||||
if suppliedKey != key {
|
|
||||||
hdlr.respondIRCError(
|
|
||||||
writer, request, clientID, sessionID,
|
|
||||||
irc.ErrBadChannelKey, nick,
|
|
||||||
[]string{channel},
|
|
||||||
"Cannot join channel (+k)",
|
|
||||||
)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. User limit check (+l).
|
|
||||||
limit, limErr := hdlr.params.Database.
|
|
||||||
GetChannelUserLimit(ctx, chID)
|
|
||||||
if limErr == nil && limit > 0 {
|
|
||||||
count, cntErr := hdlr.params.Database.
|
|
||||||
CountChannelMembers(ctx, chID)
|
|
||||||
if cntErr == nil && count >= int64(limit) {
|
|
||||||
hdlr.respondIRCError(
|
|
||||||
writer, request, clientID, sessionID,
|
|
||||||
irc.ErrChannelIsFull, nick,
|
|
||||||
[]string{channel},
|
|
||||||
"Cannot join channel (+l)",
|
|
||||||
)
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deliverJoinNumerics sends RPL_TOPIC/RPL_NOTOPIC,
|
// deliverJoinNumerics sends RPL_TOPIC/RPL_NOTOPIC,
|
||||||
// RPL_NAMREPLY, and RPL_ENDOFNAMES to the joining client.
|
// RPL_NAMREPLY, and RPL_ENDOFNAMES to the joining client.
|
||||||
func (hdlr *Handlers) deliverJoinNumerics(
|
func (hdlr *Handlers) deliverJoinNumerics(
|
||||||
@@ -4091,3 +4010,76 @@ func (hdlr *Handlers) deliverWhoisIdle(
|
|||||||
"seconds idle, signon time",
|
"seconds idle, signon time",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fanOut inserts a message and enqueues it to all given
|
||||||
|
// sessions.
|
||||||
|
func (hdlr *Handlers) fanOut(
|
||||||
|
request *http.Request,
|
||||||
|
command, from, target string,
|
||||||
|
body json.RawMessage,
|
||||||
|
meta json.RawMessage,
|
||||||
|
sessionIDs []int64,
|
||||||
|
) (string, error) {
|
||||||
|
dbID, msgUUID, err := hdlr.params.Database.InsertMessage(
|
||||||
|
request.Context(), command, from, target,
|
||||||
|
nil, body, meta,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("insert message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sid := range sessionIDs {
|
||||||
|
enqErr := hdlr.params.Database.EnqueueToSession(
|
||||||
|
request.Context(), sid, dbID,
|
||||||
|
)
|
||||||
|
if enqErr != nil {
|
||||||
|
hdlr.log.Error("enqueue failed",
|
||||||
|
"error", enqErr, "session_id", sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
hdlr.broker.Notify(sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgUUID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// fanOutSilent calls fanOut and discards the UUID.
|
||||||
|
func (hdlr *Handlers) fanOutSilent(
|
||||||
|
request *http.Request,
|
||||||
|
command, from, target string,
|
||||||
|
body json.RawMessage,
|
||||||
|
sessionIDs []int64,
|
||||||
|
) error {
|
||||||
|
_, err := hdlr.fanOut(
|
||||||
|
request, command, from, target,
|
||||||
|
body, nil, sessionIDs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// requireChannelOp checks if the session is a channel
|
||||||
|
// operator and sends ERR_CHANOPRIVSNEEDED if not.
|
||||||
|
func (hdlr *Handlers) requireChannelOp(
|
||||||
|
writer http.ResponseWriter,
|
||||||
|
request *http.Request,
|
||||||
|
sessionID, clientID int64,
|
||||||
|
nick, channel string,
|
||||||
|
chID int64,
|
||||||
|
) bool {
|
||||||
|
isOp, err := hdlr.params.Database.IsChannelOperator(
|
||||||
|
request.Context(), chID, sessionID,
|
||||||
|
)
|
||||||
|
if err != nil || !isOp {
|
||||||
|
hdlr.respondIRCError(
|
||||||
|
writer, request, clientID, sessionID,
|
||||||
|
irc.ErrChanOpPrivsNeeded, nick,
|
||||||
|
[]string{channel},
|
||||||
|
"You're not channel operator",
|
||||||
|
)
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ func (c *Conn) joinChannel(
|
|||||||
channel string,
|
channel string,
|
||||||
) {
|
) {
|
||||||
result, err := c.svc.JoinChannel(
|
result, err := c.svc.JoinChannel(
|
||||||
ctx, c.sessionID, c.nick, channel,
|
ctx, c.sessionID, c.nick, channel, "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendIRCError(err)
|
c.sendIRCError(err)
|
||||||
|
|||||||
@@ -140,6 +140,18 @@ func (s *Service) SendChannelMessage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ban check — banned users cannot send messages.
|
||||||
|
isBanned, banErr := s.DB.IsSessionBanned(
|
||||||
|
ctx, chID, sessionID,
|
||||||
|
)
|
||||||
|
if banErr == nil && isBanned {
|
||||||
|
return 0, "", &IRCError{
|
||||||
|
irc.ErrCannotSendToChan,
|
||||||
|
[]string{channel},
|
||||||
|
"Cannot send to channel (+b)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
moderated, _ := s.DB.IsChannelModerated(ctx, chID)
|
moderated, _ := s.DB.IsChannelModerated(ctx, chID)
|
||||||
if moderated {
|
if moderated {
|
||||||
isOp, _ := s.DB.IsChannelOperator(
|
isOp, _ := s.DB.IsChannelOperator(
|
||||||
@@ -214,7 +226,7 @@ func (s *Service) SendDirectMessage(
|
|||||||
func (s *Service) JoinChannel(
|
func (s *Service) JoinChannel(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
sessionID int64,
|
sessionID int64,
|
||||||
nick, channel string,
|
nick, channel, suppliedKey string,
|
||||||
) (*JoinResult, error) {
|
) (*JoinResult, error) {
|
||||||
chID, err := s.DB.GetOrCreateChannel(ctx, channel)
|
chID, err := s.DB.GetOrCreateChannel(ctx, channel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -226,6 +238,15 @@ func (s *Service) JoinChannel(
|
|||||||
)
|
)
|
||||||
isCreator := countErr == nil && memberCount == 0
|
isCreator := countErr == nil && memberCount == 0
|
||||||
|
|
||||||
|
if !isCreator {
|
||||||
|
if joinErr := checkJoinRestrictions(
|
||||||
|
ctx, s.DB, chID, sessionID,
|
||||||
|
channel, suppliedKey, memberCount,
|
||||||
|
); joinErr != nil {
|
||||||
|
return nil, joinErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if isCreator {
|
if isCreator {
|
||||||
err = s.DB.JoinChannelAsOperator(
|
err = s.DB.JoinChannelAsOperator(
|
||||||
ctx, chID, sessionID,
|
ctx, chID, sessionID,
|
||||||
@@ -238,6 +259,9 @@ func (s *Service) JoinChannel(
|
|||||||
return nil, fmt.Errorf("join channel: %w", err)
|
return nil, fmt.Errorf("join channel: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear invite after successful join.
|
||||||
|
_ = s.DB.ClearChannelInvite(ctx, chID, sessionID)
|
||||||
|
|
||||||
memberIDs, _ := s.DB.GetChannelMemberIDs(ctx, chID)
|
memberIDs, _ := s.DB.GetChannelMemberIDs(ctx, chID)
|
||||||
body, _ := json.Marshal([]string{channel}) //nolint:errchkjson
|
body, _ := json.Marshal([]string{channel}) //nolint:errchkjson
|
||||||
|
|
||||||
@@ -651,6 +675,18 @@ func (s *Service) SetChannelFlag(
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("set topic locked: %w", err)
|
return fmt.Errorf("set topic locked: %w", err)
|
||||||
}
|
}
|
||||||
|
case 'i':
|
||||||
|
if err := s.DB.SetChannelInviteOnly(
|
||||||
|
ctx, chID, setting,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("set invite only: %w", err)
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
if err := s.DB.SetChannelSecret(
|
||||||
|
ctx, chID, setting,
|
||||||
|
); err != nil {
|
||||||
|
return fmt.Errorf("set secret: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -743,3 +779,61 @@ func (s *Service) broadcastNickChange(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkJoinRestrictions validates Tier 2 join conditions:
|
||||||
|
// bans, invite-only, channel key, and user limit.
|
||||||
|
func checkJoinRestrictions(
|
||||||
|
ctx context.Context,
|
||||||
|
database *db.Database,
|
||||||
|
chID, sessionID int64,
|
||||||
|
channel, suppliedKey string,
|
||||||
|
memberCount int64,
|
||||||
|
) error {
|
||||||
|
isBanned, banErr := database.IsSessionBanned(
|
||||||
|
ctx, chID, sessionID,
|
||||||
|
)
|
||||||
|
if banErr == nil && isBanned {
|
||||||
|
return &IRCError{
|
||||||
|
Code: irc.ErrBannedFromChan,
|
||||||
|
Params: []string{channel},
|
||||||
|
Message: "Cannot join channel (+b)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isInviteOnly, ioErr := database.IsChannelInviteOnly(
|
||||||
|
ctx, chID,
|
||||||
|
)
|
||||||
|
if ioErr == nil && isInviteOnly {
|
||||||
|
hasInvite, invErr := database.HasChannelInvite(
|
||||||
|
ctx, chID, sessionID,
|
||||||
|
)
|
||||||
|
if invErr != nil || !hasInvite {
|
||||||
|
return &IRCError{
|
||||||
|
Code: irc.ErrInviteOnlyChan,
|
||||||
|
Params: []string{channel},
|
||||||
|
Message: "Cannot join channel (+i)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key, keyErr := database.GetChannelKey(ctx, chID)
|
||||||
|
if keyErr == nil && key != "" && suppliedKey != key {
|
||||||
|
return &IRCError{
|
||||||
|
Code: irc.ErrBadChannelKey,
|
||||||
|
Params: []string{channel},
|
||||||
|
Message: "Cannot join channel (+k)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limit, limErr := database.GetChannelUserLimit(ctx, chID)
|
||||||
|
if limErr == nil && limit > 0 &&
|
||||||
|
memberCount >= int64(limit) {
|
||||||
|
return &IRCError{
|
||||||
|
Code: irc.ErrChannelIsFull,
|
||||||
|
Params: []string{channel},
|
||||||
|
Message: "Cannot join channel (+l)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func TestJoinChannel(t *testing.T) {
|
|||||||
sid := createSession(ctx, t, env.db, "alice")
|
sid := createSession(ctx, t, env.db, "alice")
|
||||||
|
|
||||||
result, err := env.svc.JoinChannel(
|
result, err := env.svc.JoinChannel(
|
||||||
ctx, sid, "alice", "#general",
|
ctx, sid, "alice", "#general", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("JoinChannel: %v", err)
|
t.Fatalf("JoinChannel: %v", err)
|
||||||
@@ -185,7 +185,7 @@ func TestJoinChannel(t *testing.T) {
|
|||||||
sid2 := createSession(ctx, t, env.db, "bob")
|
sid2 := createSession(ctx, t, env.db, "bob")
|
||||||
|
|
||||||
result2, err := env.svc.JoinChannel(
|
result2, err := env.svc.JoinChannel(
|
||||||
ctx, sid2, "bob", "#general",
|
ctx, sid2, "bob", "#general", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("JoinChannel bob: %v", err)
|
t.Fatalf("JoinChannel bob: %v", err)
|
||||||
@@ -207,7 +207,7 @@ func TestPartChannel(t *testing.T) {
|
|||||||
sid := createSession(ctx, t, env.db, "alice")
|
sid := createSession(ctx, t, env.db, "alice")
|
||||||
|
|
||||||
_, err := env.svc.JoinChannel(
|
_, err := env.svc.JoinChannel(
|
||||||
ctx, sid, "alice", "#general",
|
ctx, sid, "alice", "#general", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("JoinChannel: %v", err)
|
t.Fatalf("JoinChannel: %v", err)
|
||||||
@@ -242,14 +242,14 @@ func TestSendChannelMessage(t *testing.T) {
|
|||||||
sid2 := createSession(ctx, t, env.db, "bob")
|
sid2 := createSession(ctx, t, env.db, "bob")
|
||||||
|
|
||||||
_, err := env.svc.JoinChannel(
|
_, err := env.svc.JoinChannel(
|
||||||
ctx, sid1, "alice", "#chat",
|
ctx, sid1, "alice", "#chat", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join alice: %v", err)
|
t.Fatalf("join alice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = env.svc.JoinChannel(
|
_, err = env.svc.JoinChannel(
|
||||||
ctx, sid2, "bob", "#chat",
|
ctx, sid2, "bob", "#chat", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join bob: %v", err)
|
t.Fatalf("join bob: %v", err)
|
||||||
@@ -293,14 +293,14 @@ func TestBroadcastQuit(t *testing.T) {
|
|||||||
sid2 := createSession(ctx, t, env.db, "bob")
|
sid2 := createSession(ctx, t, env.db, "bob")
|
||||||
|
|
||||||
_, err := env.svc.JoinChannel(
|
_, err := env.svc.JoinChannel(
|
||||||
ctx, sid1, "alice", "#room",
|
ctx, sid1, "alice", "#room", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join alice: %v", err)
|
t.Fatalf("join alice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = env.svc.JoinChannel(
|
_, err = env.svc.JoinChannel(
|
||||||
ctx, sid2, "bob", "#room",
|
ctx, sid2, "bob", "#room", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join bob: %v", err)
|
t.Fatalf("join bob: %v", err)
|
||||||
@@ -326,14 +326,14 @@ func TestSendChannelMessage_Moderated(t *testing.T) {
|
|||||||
sid2 := createSession(ctx, t, env.db, "bob")
|
sid2 := createSession(ctx, t, env.db, "bob")
|
||||||
|
|
||||||
result, err := env.svc.JoinChannel(
|
result, err := env.svc.JoinChannel(
|
||||||
ctx, sid1, "alice", "#modchat",
|
ctx, sid1, "alice", "#modchat", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join alice: %v", err)
|
t.Fatalf("join alice: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = env.svc.JoinChannel(
|
_, err = env.svc.JoinChannel(
|
||||||
ctx, sid2, "bob", "#modchat",
|
ctx, sid2, "bob", "#modchat", "",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("join bob: %v", err)
|
t.Fatalf("join bob: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user