~nicoco/slidgnal#2: 
Group sending failure

I sent a message to a Signal group chat from Slidge and got an XMPP error back. Conversations says "Send Failure"; I can see it in the Signal app, though it message details there show that 6 people failed to send, 17 were received, 4 were sent but not yet received.

If I send a message to this group from the official signal app the message details show that everyone in the group received it.

In the server logs I see

Sep 25 14:33:45 xmpp.example.org slidgnal[4910]: DEBUG:Signal:Received payload:
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]: {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:     "data": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:         "results": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": false,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": true
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "proof_required_failure": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "message": "org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException: StatusCode: 428",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "options": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "PUSH_CHALLENGE",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "RECAPTCHA"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "retry_after": 86400,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "token": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "proof_required_failure": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "message": "org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException: StatusCode: 428",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "options": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "PUSH_CHALLENGE",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "RECAPTCHA"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "retry_after": 86400,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "token": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "proof_required_failure": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "message": "org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException: StatusCode: 428",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "options": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "PUSH_CHALLENGE",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "RECAPTCHA"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "retry_after": 86400,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "token": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": false,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": true
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "number": "[redacted]",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "proof_required_failure": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "message": "org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException: StatusCode: 428",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "options": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "PUSH_CHALLENGE",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         "RECAPTCHA"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "retry_after": 86400,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "token": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "number": "[redacted]",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": false,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "success": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "devices": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         1,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         2
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "duration": 106,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "needsSync": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "unidentified": true
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "address": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "number": "[redacted]",
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "uuid": "[redacted]"
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "networkFailure": false,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "success": {
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "devices": [
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                         1
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     ],
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "duration": 109,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "needsSync": true,
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                     "unidentified": true
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 },
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:                 "unregisteredFailure": false
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]:             },
[...]
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]: ERROR:slidge.core.gateway.session_dispatcher:Failed to handle incoming stanza: <message id="b352f9c2-8012-464f-a77d-29a977f1b3a0" from="user@example.org/Cheogram.QqZa" type="groupchat" xml:lang="en" to="ir3vkzlwkevuk4tdkn3esmtwiz3xorlzgjevemsmlbwhgtcciffu6sdkoftgkocumrdukpi=@signal.example.org"><body>I don&apos;t know about that one Imogen</body><markable xmlns="urn:xmpp:chat-markers:0" /></message>
[...]
Sep 25 14:33:45 xmpp.example.org slidgnal[4910]: NameError: name 'ProofRequiredError' is not defined

The full logs / screenshots are too large to post here but I'll put them up on github and link them.

So there's a couple things here:

  1. Why did the message fail to some people?
  2. When it did fail, is it possible to give a better respond than "send failure"? That encourages me to resend the message, which would end up spamming the group inadvertantly
  3. Should there be some code that knows how to handle ProofRequiredError? What does it want anything -- it wants me to..solve a captcha? Weird??
Status
REPORTED
Submitter
~kousu
Assigned to
No-one
Submitted
9 months ago
Updated
8 months ago
Labels
bug

~kousu 9 months ago*

~nicoco 9 months ago

Thanks for the report.

I see two interesting things in your logs:

  1. The NameError which is a sign that my signald wrapper has an issue building the dataclass instance for this payload.
  2. The fact that you apparently need to solve another captcha, if we believe [https://gitlab.com/signald/signald/-/issues/374](this issue).

I think the best behaviour here would be to have the gateway send you a message: "you need to complete a captcha and paste the redirect URL as a reply here", which is not great at all UX-wise but I don't think I can do better.

In the meantime, if you want to try out if it this would work and don't mind using signaldctl, it's probably possible to use it to "solve the challenge".

~kousu 9 months ago*

It seems that the error is at least reproducible: for this particular Signal group, the same recipients are giving me trouble on each Slidge message.

I didn't know that Signal had captchas! I've never run into them before. I guess because I've never done anything too unusual with Signal.

I tried to follow the instructions but failed; do I need to deregister first? If I re-register signald from the command line how do I get slidge hooked back up to the same account?

[root@xmpp ~]# signaldctl account register [redacted] --captcha 'signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.challenge.P1_[redacted]'
2023/09/26 14:13:16 error registering with server

~nicoco 9 months ago

Here you are trying to register a new account, which has been broken in signald for a while. You are looking for a different "flow" of catpcha here. Possibly, signald does not expose it nicely in its CLI, but you can send "raw json" payloads to the signald daemon.

(I agree it's not great at all UX wise, but if you could test if it solves the issue for you, I can make a nicer wrapper via a slidge command).

~kousu 9 months ago

Oh I see. But I'm not sure what to put in as the "challenge". I tried using the "token" I got from the error:

Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                 "proof_required_failure": {
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                     "message": "org.whispersystems.signalservice.api.push.exceptions.ProofRequiredException: StatusCode: 428",
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                     "options": [
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                         "PUSH_CHALLENGE",
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                         "RECAPTCHA"
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                     ],
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                     "retry_after": 86400,
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                     "token": "687f7311-2330-4058-b835-e153512cf8df"
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                 },
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:                 "unregisteredFailure": false
Sep 27 04:14:15 comms2.kousu.ca slidgnal[4910]:             },

but it failed:

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "captcha_token": "signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.registration.P1_eyJ0[redacted]", "challenge": "687f7311-2330-4058-b835-e153512cf8df"}'
Error: signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException"],"message":"org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException: [400] Bad response: 400 "}
Usage:
  signaldctl raw version type [json] [flags]

Flags:
  -h, --help   help for raw

Global Flags:
      --config string          config file (default "/root/.config/signaldctl.yaml")
  -o, --output-format string   the output format. options are usually table, yaml and json, default is usually table. Some commands have other options. (default "default")
      --socket string          the path to the signald socket file

signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException"],"message":"org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException: [400] Bad response: 400 "}

then I tried using the UUID that was embedded in the output from https://signalcaptchas.org/registration/generate -- and removing it from captcha_token -- but it didn't work either

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "captcha_token": "eyJ0[redacted]", "challenge": "5fad97ac-7d06-4e44-b18a-b950b20148ff"}'
Error: signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException"],"message":"org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException: [400] Bad response: 400 "}
Usage:
  signaldctl raw version type [json] [flags]

Flags:
  -h, --help   help for raw

Global Flags:
      --config string          config file (default "/root/.config/signaldctl.yaml")
  -o, --output-format string   the output format. options are usually table, yaml and json, default is usually table. Some commands have other options. (default "default")
      --socket string          the path to the signald socket file

signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException"],"message":"org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException: [400] Bad response: 400 "}

https://signalcaptchas.org/registration/generate seems like the wrong link to use: that's for registration, and puts the word "registration" in the output. I'm not trying to register, I'm trying to answer a challenge.

And do you think I need to answer a separate challenge for each recipient that rejected me? In the original logs you can see the 428 responses are associated with individual recipients, and each of them says "options": [ "PUSH_CHALLENGE", "RECAPTCHA" ], as if I need to do one challenge for each.

~nicoco 9 months ago

Try with this URL: https://signalcaptchas.org/challenge/generate to generate the captcha

Using the token from the error message sounds good, maybe it expires at some point though?

Disclaimer: I am not sure of anything I wrote :-)

~kousu 9 months ago

Well, it accepts captchas generated with that link!

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "challenge": "c27f5d96-b4cb-4ed2-91a9-9434f4135230", "captcha_token": "signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.challenge.P1_eyJ0e[redacted]"}'
{}[root@xmpp ~]# echo $?
0

And resubmitting the same captcha gives an error:

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "challenge": "c27f5d96-b4cb-4ed2-91a9-9434f4135230", "captcha_token": "signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.challenge.P1_eyJ0e[redacted]"}'
Error: signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException","com.fasterxml.jackson.databind.exc.MismatchedInputException"],"message":"org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException: Unable to parse entity"}
Usage:
  signaldctl raw version type [json] [flags]

Flags:
  -h, --help   help for raw

Global Flags:
      --config string          config file (default "/root/.config/signaldctl.yaml")
  -o, --output-format string   the output format. options are usually table, yaml and json, default is usually table. Some commands have other options. (default "default")
      --socket string          the path to the signald socket file

signald error: {"exceptions":["org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException","com.fasterxml.jackson.databind.exc.MismatchedInputException"],"message":"org.whispersystems.signalservice.api.push.exceptions.MalformedResponseException: Unable to parse entity"}

but nothing I submit makes a difference to. In fact it accepts anything from uuidgen, or not even using a UUID at all as challenge.

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "challenge": "signalisconfusing", "captcha_token": "signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.challenge.P1_eyJ0e[redacted]"}'
{}[root@xmpp ~]# echo $?
0

This seems hard! We'll probably have to go diving through https://github.com/signalapp/Signal-Server to figure it out.

~kousu 9 months ago*

It seems like my original instincts were in the right direction; I found this:

public class AnswerRecaptchaChallengeRequest extends AnswerChallengeRequest {

  @Schema(description = "The value of the token field from the server's 428 response")
  @NotBlank
  private String token;

  @Schema(
      description = "A string representing a solved captcha",
      example = "signal-hcaptcha.30b01b46-d8c9-4c30-bbd7-9719acfe0c10.challenge.abcdefg1345")
  @NotBlank
  private String captcha;

So yes, the token returned in the ProofRequiredError is somehow involved. That data structure is named "recaptcha" but the example says "hcaptcha" so I think that's the right code, it's probably just bitrotted a bit.

Over in this seems to be the handler that is responding to me which indeed uses AnswerRecaptchaChallengeRequest:

ChallengeController.java

@Path("/v1/challenge")
@Tag(name = "Challenge")
public class ChallengeController {

  private final RateLimitChallengeManager rateLimitChallengeManager;

  private static final String CHALLENGE_RESPONSE_COUNTER_NAME = name(ChallengeController.class, "challengeResponse");
  private static final String CHALLENGE_TYPE_TAG = "type";

  public ChallengeController(final RateLimitChallengeManager rateLimitChallengeManager) {
    this.rateLimitChallengeManager = rateLimitChallengeManager;
  }

  @PUT
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  @Operation(
      summary = "Submit proof of a challenge completion",
      description = """
          Some server endpoints (the "send message" endpoint, for example) may return a 428 response indicating the client must complete a challenge before continuing.
          Clients may use this endpoint to provide proof of a completed challenge. If successful, the client may then 
          continue their original operation.
          """,
      requestBody = @RequestBody(content = {@Content(schema = @Schema(oneOf = {AnswerPushChallengeRequest.class,
          AnswerRecaptchaChallengeRequest.class}))})
  )
  @ApiResponse(responseCode = "200", description = "Indicates the challenge proof was accepted")
  @ApiResponse(responseCode = "413", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  public Response handleChallengeResponse(@Auth final AuthenticatedAccount auth,
      @Valid final AnswerChallengeRequest answerRequest,
      @HeaderParam(HttpHeaders.X_FORWARDED_FOR) final String forwardedFor,
      @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent) throws RateLimitExceededException, IOException {

    Tags tags = Tags.of(UserAgentTagUtil.getPlatformTag(userAgent));

    try {
      if (answerRequest instanceof final AnswerPushChallengeRequest pushChallengeRequest) {
        tags = tags.and(CHALLENGE_TYPE_TAG, "push");

        rateLimitChallengeManager.answerPushChallenge(auth.getAccount(), pushChallengeRequest.getChallenge());
      } else if (answerRequest instanceof AnswerRecaptchaChallengeRequest recaptchaChallengeRequest) {
        tags = tags.and(CHALLENGE_TYPE_TAG, "recaptcha");

        final String mostRecentProxy = HeaderUtils.getMostRecentProxy(forwardedFor).orElseThrow(() -> new BadRequestException());
        boolean success = rateLimitChallengeManager.answerRecaptchaChallenge(
            auth.getAccount(),
            recaptchaChallengeRequest.getCaptcha(),
            mostRecentProxy,
            userAgent);

        if (!success) {
          return Response.status(428).build();
        }

      } else {
        tags = tags.and(CHALLENGE_TYPE_TAG, "unrecognized");
      }
    } finally {
      Metrics.counter(CHALLENGE_RESPONSE_COUNTER_NAME, tags).increment();
    }

    return Response.status(200).build();
  }

  @POST
  @Path("/push")
  @Operation(
      summary = "Request a push challenge",
      description = """
          Clients may proactively request a push challenge by making an empty POST request. Push challenges will only be
          sent to the requesting account’s main device. When the push is received it may be provided as proof of completed 
          challenge to /v1/challenge.
          APNs challenge payloads will be formatted as follows:
          ```
          {
              "aps": {
                  "sound": "default",
                  "alert": {
                      "loc-key": "APN_Message"
                  }
              },
              "rateLimitChallenge": "{CHALLENGE_TOKEN}"
          }
          ```
          FCM challenge payloads will be formatted as follows: 
          ```
          {"rateLimitChallenge": "{CHALLENGE_TOKEN}"}
          ```

          Clients may retry the PUT in the event of an HTTP/5xx response (except HTTP/508) from the server, but must 
          implement an exponential back-off system and limit the total number of retries.
          """
  )
  @ApiResponse(responseCode = "200", description = """
      Indicates a payload to the account's primary device has been attempted. When clients receive a challenge push
      notification, they may issue a PUT request to /v1/challenge.
      """)
  @ApiResponse(responseCode = "404", description = """
      The server does not have a push notification token for the authenticated account’s main device; clients may add a push
      token and try again
      """)
  @ApiResponse(responseCode = "413", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  public Response requestPushChallenge(@Auth final AuthenticatedAccount auth) {
    try {
      rateLimitChallengeManager.sendPushChallenge(auth.getAccount());
      return Response.status(200).build();
    } catch (final NotPushRegisteredException e) {
      return Response.status(404).build();
    }
  }
}

I am looking at this code, and comparing it to signald, and wondering if maybe the fields aren't named right? Signal-Server has "token" and "captcha", and Signald has "challenge" and "captcha_token".

I tried using the names Signal-Server uses but signald(?) rejected me:

[root@comms2 ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "token": "1d82892d-352a-4424-82c2-14cc6c9f88b0", "captcha": "signal-hcaptcha.1d82892d-352a-4424-82c2-14cc6c9f88b0.challenge.P1_eyJ0e[redacted]"}'
Error: signald error: {"validationResults":["missing required argument: challenge"],"message":"input validation failed, please check the request and try again."}

I discovered challenge does seem to be in the signal-hcaptcha..... format, because if I submit a correctly captcha response from https://signalcaptchas.org/challenge/generate -- without the signalcaptcha:// -- then it returns {}, but if I mangle a single character or prepend signalcaptcha:// then it errors.

I also discovered captcha_token isn't necessary -- at least, leaving it off doesn't provoke an error:

[root@xmpp ~]# signaldctl raw v1 submit_challenge '{"account": "[redacted]", "challenge": "signal-hcaptcha.5fad97ac-7d06-4e44-b18a-b950b20148ff.challenge.P1_eyJ0[redacted]"}'
{}[root@xmpp ~]#

But so far I haven't figured out how to actually get unblocked.

Maybe I need to submit the same challenge (i.e. captcha response) for each of the tokens for the recipients that are blocking me, before Signal-Server will unblock me to each of them?

~kousu 9 months ago

I discovered challenge does seem to be in the signal-hcaptcha..... format,

hang on, this contradicts what I just said above, that challenge can be in any format and it's captcha_token that should have the signed captcha response in it.

I've got myself rate limited so I can't test again to be sure which way it is right now.

~nicoco 8 months ago

Any update on this? Did you manage to get unblocked? I'd happily provide an (XMPP) interface to give the challenge result, but I'm not even sure how this stuff works :-/

Register here or Log in to comment, or comment via email.