Files
mrcharles f13302778b feat(cli): search, messages send, webhook verify, sso verify
The CLI now covers the whole Redmine 507 surface: `search TERM
[-page N]` (operators pass through quoted), `messages send -title
-to user1,user2 [-group/-email] [-file|-raw]`, plus the two secret
helpers `webhook verify SIG` (delivery body on stdin, exit 0 only
on a valid HMAC) and `sso verify SSO SIG` (prints the decoded
Discourse Connect payload as JSON). The helpers run before client
construction so they work with only their secret env set, no
instance credentials needed. Usage text lists the new env vars.

Part of Redmine 507 (Discourse Go client).
2026-08-29 16:50:31 -05:00

71 lines
2.0 KiB
Go

// Secret-helper commands: they never touch an instance, so they run
// before client construction and read only their own env secrets.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"strings"
"git.knownelement.com/ukrrs/mopac-discourse-go"
)
// runWebhook implements `webhook verify SIG`: the raw delivery body on
// stdin, the X-Discourse-Event-Signature value as the argument, the
// secret from DISCOURSE_WEBHOOK_SECRET. Exit 0 = signature valid.
func runWebhook(args []string) int {
if len(args) != 2 || args[0] != "verify" {
fmt.Fprintf(os.Stderr, "webhook needs: verify SIG (delivery body on stdin)\n\n%s", usage)
return 1
}
secret, err := discourse.WebhookSecretFromEnv()
if err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: %v\n", err)
return 1
}
body, err := io.ReadAll(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: read stdin: %v\n", err)
return 1
}
if err := discourse.VerifyWebhook(secret, body, args[1]); err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: %v\n", err)
return 2
}
fmt.Printf("verified %d bytes\n", len(body))
return 0
}
// runSSO implements `sso verify SSO SIG`: checks a Discourse Connect
// login redirect's sso/sig pair against DISCOURSE_SSO_SECRET and prints
// the decoded payload as JSON. Exit 0 = signature valid.
func runSSO(args []string) int {
if len(args) != 3 || args[0] != "verify" {
fmt.Fprintf(os.Stderr, "sso needs: verify SSO SIG (sso+sig from the login URL)\n\n%s", usage)
return 1
}
secret, err := discourse.SSOSecretFromEnv()
if err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: %v\n", err)
return 1
}
vals, err := discourse.VerifySSO(secret, args[1], args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: %v\n", err)
return 2
}
obj := map[string]string{}
for k, v := range vals {
obj[k] = strings.Join(v, ",")
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(obj); err != nil {
fmt.Fprintf(os.Stderr, "discourse-go: encode payload: %v\n", err)
return 1
}
return 0
}