From 97e247d25c1dcf36fa7206259f00a0e7425827d3 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Mon, 6 Jul 2026 14:56:25 -0400 Subject: [PATCH 1/2] bot fixup --- base/git/git.go | 8 +++-- gcalbot/gcalbot/handler.go | 4 ++- gitlabbot/gitlabbot/handler.go | 9 ++++++ jirabot/src/cmd-config.ts | 58 ++++++++++++++++++++++++++++++++-- pollbot/pollbot/http.go | 4 ++- triviabot/triviabot/handler.go | 31 ++++++++++++------ 6 files changed, 98 insertions(+), 16 deletions(-) diff --git a/base/git/git.go b/base/git/git.go index fe860752..46b535c6 100644 --- a/base/git/git.go +++ b/base/git/git.go @@ -20,9 +20,13 @@ const ( func RefToName(ref string) (branch string) { // refs are always given in the form "refs/heads/{branch name}" or "refs/tags/{tag name}" - branch = strings.Split(ref, "refs/")[1] + parts := strings.SplitN(ref, "refs/", 2) + if len(parts) < 2 { + return ref + } + branch = parts[1] if strings.HasPrefix(branch, "heads/") { - branch = strings.Split(branch, "heads/")[1] + branch = strings.SplitN(branch, "heads/", 2)[1] } // if we got a tag ref, just leave it as "tags/{tag name}" return branch diff --git a/gcalbot/gcalbot/handler.go b/gcalbot/gcalbot/handler.go index 8f7c1ed5..ab448bb9 100644 --- a/gcalbot/gcalbot/handler.go +++ b/gcalbot/gcalbot/handler.go @@ -160,5 +160,7 @@ func (h *Handler) handleConfigure(msg chat1.MsgSummary) error { } func (h *Handler) LoginToken(username string) string { - return hex.EncodeToString(hmac.New(sha256.New, []byte(h.tokenSecret)).Sum([]byte(username))) + mac := hmac.New(sha256.New, []byte(h.tokenSecret)) + mac.Write([]byte(username)) + return hex.EncodeToString(mac.Sum(nil)) } diff --git a/gitlabbot/gitlabbot/handler.go b/gitlabbot/gitlabbot/handler.go index 033afd0e..431a53a1 100644 --- a/gitlabbot/gitlabbot/handler.go +++ b/gitlabbot/gitlabbot/handler.go @@ -88,6 +88,15 @@ func (h *Handler) handleSubscribe(ctx context.Context, cmd string, msg chat1.Msg return nil } + isAllowed, err := base.IsAtLeastWriter(h.kbc, msg.Sender.Username, msg.Channel) + if err != nil { + return fmt.Errorf("error getting role status: %s", err) + } + if !isAllowed { + h.ChatEcho(msg.ConvID, "You must be at least a writer to configure me!") + return nil + } + hostedURL, repo, err := parseRepoInput(args[0]) if err != nil { h.ChatEcho(msg.ConvID, "Invalid repo: %q, expected `` or `https://domain.com/owner/repo`", repo) diff --git a/jirabot/src/cmd-config.ts b/jirabot/src/cmd-config.ts index 09d03a42..fa0113d8 100644 --- a/jirabot/src/cmd-config.ts +++ b/jirabot/src/cmd-config.ts @@ -6,6 +6,22 @@ import * as JiraOauth from './jira-oauth' import * as Utils from './utils' import * as Jira from './jira' +const isPrivateHost = (hostname: string): boolean => { + if (hostname === 'localhost' || hostname === '::1') return true + const ipv4 = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) + if (ipv4) { + const [a, b] = [Number(ipv4[1]), Number(ipv4[2])] + if (a === 10) return true + if (a === 172 && b >= 16 && b <= 31) return true + if (a === 192 && b === 168) return true + if (a === 127) return true + if (a === 169 && b === 254) return true + if (a === 0) return true + } + if (/\.(local|internal|corp|lan|intranet)$/.test(hostname)) return true + return false +} + const makeNewTeamChannelConfig = async ( context: Context, messageContext: Message.MessageContext, @@ -244,8 +260,43 @@ const handleTeamConfig = async ( return Errors.makeResult(undefined) } switch (parsedMessage.toSet.name) { - case 'jiraHost': - // TODO check admin + case 'jiraHost': { + const teamDetails = await context.bot.team.listTeamMemberships({ + team: parsedMessage.context.teamName, + }) + const isAdmin = [ + ...(teamDetails.members.owners || []), + ...(teamDetails.members.admins || []), + ].some(m => m.username === parsedMessage.context.senderUsername) + if (!isAdmin) { + replyChat( + context, + parsedMessage, + 'You must be a team admin to configure the Jira server.' + ) + return Errors.makeError(undefined) + } + + const rawHost = parsedMessage.toSet.value.replace(/\/+$/, '') + let parsedURL: URL + try { + parsedURL = new URL( + rawHost.startsWith('http') ? rawHost : `https://${rawHost}` + ) + } catch { + replyChat(context, parsedMessage, `Invalid jiraHost: ${rawHost}`) + return Errors.makeError(undefined) + } + const hostname = parsedURL.hostname + if (isPrivateHost(hostname)) { + replyChat( + context, + parsedMessage, + `jiraHost must be a public hostname, not an internal address.` + ) + return Errors.makeError(undefined) + } + const detailsRet = await JiraOauth.generateNewJiraLinkDetails() if (detailsRet.type === Errors.ReturnType.Error) { Errors.reportErrorAndReplyChat( @@ -258,7 +309,7 @@ const handleTeamConfig = async ( const details = detailsRet.result const newConfig = { - jiraHost: parsedMessage.toSet.value.replace(/\/+$/, ''), + jiraHost: rawHost, jiraAuth: { consumerKey: details.consumerKey, publicKey: details.publicKey, @@ -287,6 +338,7 @@ const handleTeamConfig = async ( jiraConfigToMessageBody(context, newConfig) ) return Errors.makeResult(undefined) + } default: Errors.reportErrorAndReplyChat(context, parsedMessage.context, { type: Errors.ErrorType.UnknownParam, diff --git a/pollbot/pollbot/http.go b/pollbot/pollbot/http.go index da9776d7..305a38e9 100644 --- a/pollbot/pollbot/http.go +++ b/pollbot/pollbot/http.go @@ -152,5 +152,7 @@ func (h *HTTPSrv) handleImage(w http.ResponseWriter, r *http.Request) { func (h *HTTPSrv) handleHealthCheck(_ http.ResponseWriter, _ *http.Request) {} func (h *HTTPSrv) LoginToken(username string) string { - return hex.EncodeToString(hmac.New(sha256.New, []byte(h.tokenSecret)).Sum([]byte(username))) + mac := hmac.New(sha256.New, []byte(h.tokenSecret)) + mac.Write([]byte(username)) + return hex.EncodeToString(mac.Sum(nil)) } diff --git a/triviabot/triviabot/handler.go b/triviabot/triviabot/handler.go index 79a3e16a..93c05be7 100644 --- a/triviabot/triviabot/handler.go +++ b/triviabot/triviabot/handler.go @@ -123,18 +123,31 @@ func (h *Handler) HandleCommand(ctx context.Context, msg chat1.MsgSummary) error } cmd := strings.TrimSpace(msg.Content.Text.Body) switch { - case strings.HasPrefix(cmd, "!trivia begin"): - h.stats.Count("start") - h.handleStart(msg) - case strings.HasPrefix(cmd, "!trivia end"): - h.stats.Count("stop") - h.handleStop(msg) + case strings.HasPrefix(cmd, "!trivia begin"), + strings.HasPrefix(cmd, "!trivia end"), + strings.HasPrefix(cmd, "!trivia reset"): + isAllowed, err := base.IsAtLeastWriter(h.kbc, msg.Sender.Username, msg.Channel) + if err != nil { + return fmt.Errorf("error getting role status: %s", err) + } + if !isAllowed { + h.ChatEcho(msg.ConvID, "You must be at least a writer to manage trivia!") + return nil + } + switch { + case strings.HasPrefix(cmd, "!trivia begin"): + h.stats.Count("start") + h.handleStart(msg) + case strings.HasPrefix(cmd, "!trivia end"): + h.stats.Count("stop") + h.handleStop(msg) + case strings.HasPrefix(cmd, "!trivia reset"): + h.stats.Count("reset") + return h.handleReset(ctx, msg) + } case strings.HasPrefix(cmd, "!trivia top"): h.stats.Count("top") return h.handleTop(ctx, msg.ConvID) - case strings.HasPrefix(cmd, "!trivia reset"): - h.stats.Count("reset") - return h.handleReset(ctx, msg) } return nil } From efaf6edcb74fc03dd8c8f1ebc9d169df150213e0 Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Mon, 6 Jul 2026 15:00:00 -0400 Subject: [PATCH 2/2] x --- triviabot/triviabot/handler.go | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/triviabot/triviabot/handler.go b/triviabot/triviabot/handler.go index 93c05be7..7534b0d7 100644 --- a/triviabot/triviabot/handler.go +++ b/triviabot/triviabot/handler.go @@ -123,28 +123,16 @@ func (h *Handler) HandleCommand(ctx context.Context, msg chat1.MsgSummary) error } cmd := strings.TrimSpace(msg.Content.Text.Body) switch { - case strings.HasPrefix(cmd, "!trivia begin"), - strings.HasPrefix(cmd, "!trivia end"), - strings.HasPrefix(cmd, "!trivia reset"): - isAllowed, err := base.IsAtLeastWriter(h.kbc, msg.Sender.Username, msg.Channel) - if err != nil { - return fmt.Errorf("error getting role status: %s", err) - } - if !isAllowed { - h.ChatEcho(msg.ConvID, "You must be at least a writer to manage trivia!") - return nil - } - switch { - case strings.HasPrefix(cmd, "!trivia begin"): - h.stats.Count("start") - h.handleStart(msg) - case strings.HasPrefix(cmd, "!trivia end"): - h.stats.Count("stop") - h.handleStop(msg) - case strings.HasPrefix(cmd, "!trivia reset"): - h.stats.Count("reset") - return h.handleReset(ctx, msg) - } + // Trivia commands are open to all channel members, including readers. + case strings.HasPrefix(cmd, "!trivia begin"): + h.stats.Count("start") + h.handleStart(msg) + case strings.HasPrefix(cmd, "!trivia end"): + h.stats.Count("stop") + h.handleStop(msg) + case strings.HasPrefix(cmd, "!trivia reset"): + h.stats.Count("reset") + return h.handleReset(ctx, msg) case strings.HasPrefix(cmd, "!trivia top"): h.stats.Count("top") return h.handleTop(ctx, msg.ConvID)