Form draftToken with dollar sign failure

I've only been using ODK for 2 days now. I may be completely off my rocker. Just let me know. BTW, love the FP approach here in the backend.

1. What is the issue? Please be detailed.

A token key with a dollar sign in the middle was generated and Express route params is apparently chopping off the right side. This means it will not find the form correctly when calling GET /test/:key/projects/:projectId/forms/:id/draft/formList

2. What steps can we take to reproduce this issue?

Just installed ODK for the first time using odk-center docker-compose with an upstream nginx for SSL negotiation. Used odk-build to generate my first form and uploaded that. This created a draft testing QR code. Loaded this into ODK Collect and it fails to load the form. Server side logs show a hit to /v1/test/bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj$mUwq7w5MnJereDHdbBQ6zCPaA3eF/projects/1/forms/apartments/draft/formList

3. What have you tried to fix the issue?

I used curl to reproduce the call and overcame an OpenRosa header issue by calling it like this.

curl -H "X-OpenRosa-Version: 1.0" https://my.host.name/odk/v1/test/bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj$mUwq7w5MnJereDHdbBQ6zCPaA3eF/projects/1/forms/apartments/draft/formList

You will see an extra odk/ in the path because ODK is not the only service on this server. There's another nginx instance and it will strip the "odk/" from the path. I'm pretty sure this is not interfering.

I create a function called log that will print my message and print the object being passed along this pipeline.

      Forms.getByProjectAndXmlFormId(params.projectId, params.id, false, Form.DraftVersion)
      .then(log("brian 1"))
      .then(getOrNotFound)
      .then(log("brian 2"))
      .then(ensureDef)
      .then(log("brian 3"))
      .then(checkFormToken(params.key))
      .then(log("brian 4"))
      .catch(Problem.translate(Problem.user.notFound, noargs(Problem.user.failedDraftAccess)))

I get a 3 but not a 4.

And I added logging here.

  const checkFormToken = (token) => {
    console.log(`checkFormToken(${token})`);
    return rejectIf(
      ((form) => {
        console.log(`form.def.draftToken is ${form.def.draftToken}`);
        return (form.def.draftToken !== token) || isBlank(form.def.draftToken)
      }),
      noargs(Problem.user.notFound)
    );
  };

The token from the URL does not match the draft token in the form definition.

draftToken: bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj$mUwq7w5MnJereDHdbBQ6zCPaA3eF

param.key: bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj

Express documentation says it uses regexes to pluck out parameter values. Dollar sign has special meaning. Escaping the dollar sign works.

curl -H "X-OpenRosa-Version: 1.0" https://my.host.name/odk/v1/test/bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj\$mUwq7w5MnJereDHdbBQ6zCPaA3eF/projects/1/forms/apartments/draft/formList

Not sure what you want to do here. Looks like you have 2 options:

  1. prevent the creation of tokens with dollar signs
  2. escape the dollar sign in URL.

Personally, I feel #1 is better because it should not be the job of Collect (and other apps) to deal with the limitation of Express in Central backend.

If you create a ticket and point me in the right direction, I may be able to resolve this issue.

BTW is there any logging mechanism besides morgan?

1 Like

Some actual logs

central-service-1  | checkFormToken(bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj)
...
central-service-1  | form.def.draftToken is bOFFAKV3sJBmpvrqJS4oQIXgmIilrmIbSFj$mUwq7w5MnJereDHdbBQ6zCPaA3eF

I changed my mind on this. I feel the draftToken can be any string. The Collect app should be urlencoding data it puts into the URL.

client/src/util/request.js should be like this.

  serverUrlForFieldKey: (token, projectId) => {
    const encodedToken = encodeURIComponent(token);
    return `/v1/key/${encodedToken}/projects/${projectId}`;
  },

As a side note I replace /v1 with v1 top to bottom throughout the code to get the client working with relative paths. This way I can serve up all ODK stuff at /odk.

While that will be needed for whatever uses it, that's not the one for the draft testing qr code. This one needs to be change.

  serverUrlForFormDraft: (token, projectId, xmlFormId) => {
    const encodedToken = encodeURIComponent(token);
    const encodedFormId = encodeURIComponent(xmlFormId);
    return `/v1/test/${encodedToken}/projects/${projectId}/forms/${encodedFormId}/draft`;
  },

Hi @brian_252! I don't think that $ in tokens are an issue, because they have been part of Central from the start. However, if you're using curl, you'll need to escape $ or quote the URL as a whole. That's because $ is a special character in the shell.

I suspect that deploying Central at a subpath is causing the issues you're seeing. When you configure Central, DOMAIN is expected to be a fully qualified domain name and not include a subpath. One part of the issue might be that the QR code generated by the frontend doesn't include the subpath. But likely that wouldn't be the only issue, and in general, this isn't a use case that we support. Are you able to give Central its own subdomain?

1 Like