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).
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user