Skip to content

Commit

Permalink
Add submit-challenge command, inform user about ratelimiting
Browse files Browse the repository at this point in the history
  • Loading branch information
maltee1 committed May 9, 2024
1 parent b602e94 commit 644b3b1
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 2 deletions.
26 changes: 26 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func (br *SignalBridge) RegisterCommands() {
cmdInvite,
cmdListInvited,
cmdRevokeInvite,
cmdSubmitChallenge,
)
}

Expand Down Expand Up @@ -1128,3 +1129,28 @@ func fnCreate(ce *WrappedCommandEvent) {
portal.UpdateBridgeInfo(ce.Ctx)
ce.Reply("Successfully created Signal group %s", gid.String())
}

var cmdSubmitChallenge = &commands.FullHandler{
Func: wrapCommand(fnSubmitChallenge),
Name: "submit-challenge",
Help: commands.HelpMeta{
Section: HelpSectionMiscellaneous,
Description: "Submit a captcha challenge when getting rate limited",
Args: "<_captcha token_>",
},
RequiresLogin: true,
}

func fnSubmitChallenge(ce *WrappedCommandEvent) {
if len(ce.Args) == 0 {
ce.Reply("**Usage:** `submit-challenge <_captcha token_>`")
}
captcha := ce.Args[0]
captcha = strings.TrimPrefix(captcha, "signalcaptcha://")
err := ce.User.Client.SubmitRateLimitRecaptchaChallenge(ce.Ctx, captcha)
if err != nil {
ce.Reply("Failed to submit challenge: %v", err)
return
}
ce.Reply("Captcha challenge submitted successfully")
}
4 changes: 4 additions & 0 deletions messagetracking.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"maunium.net/go/mautrix/id"

"go.mau.fi/mautrix-signal/msgconv"
"go.mau.fi/mautrix-signal/pkg/signalmeow"
)

var (
Expand Down Expand Up @@ -115,6 +116,9 @@ func (portal *Portal) sendErrorMessage(ctx context.Context, evt *event.Event, er
if errors.Is(err, errMessageTakingLong) {
msg = fmt.Sprintf("\u26a0 Bridging your %s is taking longer than usual", msgType)
}
if errors.Is(err, signalmeow.ErrCaptchaChallengeRequired) {
msg = "\u26a0 You have been rate limited. Follow the instructions at https://docs.mau.fi/bridges/go/signal/captcha.html on how to complete a captcha challenge."
}
content := &event.MessageEventContent{
MsgType: event.MsgNotice,
Body: msg,
Expand Down
1 change: 1 addition & 0 deletions pkg/signalmeow/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type Client struct {
cdAuthLock sync.Mutex
cdAuth *basicExpiringCredentials
cdToken []byte
ChallengeToken string
}

func (cli *Client) handleEvent(evt events.SignalEvent) {
Expand Down
66 changes: 64 additions & 2 deletions pkg/signalmeow/sending.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,6 @@ func (cli *Client) handle410(ctx context.Context, recipient libsignalgo.ServiceI

// We got rate limited.
// We ~~will~~ could try sending a "pushChallenge" response, but if that doesn't work we just gotta wait.
// TODO: explore captcha response
func (cli *Client) handle428(ctx context.Context, recipient libsignalgo.ServiceID, response *signalpb.WebSocketResponseMessage) error {
log := zerolog.Ctx(ctx)
// Decode json body
Expand All @@ -1005,7 +1004,6 @@ func (cli *Client) handle428(ctx context.Context, recipient libsignalgo.ServiceI
log.Err(err).Msg("Unmarshal error")
return err
}

// Sample response:
//id:25 status:428 message:"Precondition Required" headers:"Retry-After:86400"
//headers:"Content-Type:application/json" headers:"Content-Length:88"
Expand All @@ -1024,6 +1022,13 @@ func (cli *Client) handle428(ctx context.Context, recipient libsignalgo.ServiceI
if retryAfterSeconds > 0 {
log.Warn().Uint64("retry_after_seconds", retryAfterSeconds).Msg("Got rate limited")
}

token := body["token"]
s, ok := token.(string)
if ok {
cli.ChallengeToken = s
}

// TODO: responding to a pushChallenge this way doesn't work, server just returns 422
// Luckily challenges seem rare when sending with sealed sender
//if body["options"] != nil {
Expand Down Expand Up @@ -1053,5 +1058,62 @@ func (cli *Client) handle428(ctx context.Context, recipient libsignalgo.ServiceI
// }
// }
//}
return ErrCaptchaChallengeRequired
}

var ErrCaptchaChallengeRequired = errors.New("captcha challenge required")

type ChallengeType string

const (
ChallengeTypeCaptcha ChallengeType = "captcha"
ChallengeTypePush ChallengeType = "rateLimitPushChallenge"
)

type SubmitChallengePayload struct {
Type ChallengeType `json:"type"`
Token string `json:"token"`
Captcha string `json:"captcha,omitempty"`
}

func (cli *Client) SubmitRateLimitRecaptchaChallenge(ctx context.Context, captcha string) error {
log := zerolog.Ctx(ctx).With().
Str("action", "submit rate limit recaptcha challenge").
Logger()
if cli.ChallengeToken == "" {
return fmt.Errorf("no pending challenge")
}
payload, err := json.Marshal(SubmitChallengePayload{
Type: ChallengeTypeCaptcha,
Token: cli.ChallengeToken,
Captcha: captcha,
})
if err != nil {
log.Err(err).Msg("Error marshalling request")
return err
}
username, password := cli.Store.BasicAuthCreds()
request := web.CreateWSRequest(http.MethodPut, "/v1/challenge", payload, &username, &password)
response, err := cli.AuthedWS.SendRequest(ctx, request)
if err != nil {
log.Err(err).Msg("Error sending request")
return err
}
responseCode := *response.Status
log.Debug().Uint32("Response Code", responseCode).Msg("Sent recaptcha challenge")
if responseCode == 428 {
return fmt.Errorf("Error submitting captcha challenge. Your captcha may have expired")
}
if responseCode != http.StatusOK && responseCode != http.StatusAccepted && responseCode != http.StatusNoContent && responseCode != http.StatusMultiStatus {
var body map[string]interface{}
err = json.Unmarshal(response.Body, &body)
if err != nil {
log.Err(err).Msg("Error unmarshalling message body")
return err
}
return fmt.Errorf("failed to submit captcha challenge. response code: %d, message: %s", responseCode, body["message"])
} else {
cli.ChallengeToken = ""
}
return nil
}

0 comments on commit 644b3b1

Please sign in to comment.