TryGhost / Ghost

Independent technology for modern publishing, memberships, subscriptions and newsletters.
https://ghost.org
MIT License
47.44k stars 10.35k forks source link

Can't fetch Members from API when email contains plus symbol #13277

Closed kerberjg closed 2 years ago

kerberjg commented 3 years ago

Issue Summary

When fetching Members through the Admin API (as suggested here: https://forum.ghost.org/t/query-member-by-email-with-admin-api/21941), and the email contains the '+' symbol (which is a standard subaddress marker), the API responds with an error:

[2021-09-02 15:39:28] ERROR "GET /ghost/api/canary/admin/members/?filter=email%3Ajames.kerber%2Btest%40localhost" 400 107ms,
    at tryCatcher (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/util.js:16:23),
    at Object.query (/var/lib/ghost/versions/4.12.1/core/server/api/shared/pipeline.js:156:24),
    at Object.query (/var/lib/ghost/versions/4.12.1/core/server/api/canary/members.js:45:59),
    at MemberRepository.list (/var/lib/ghost/versions/4.12.1/node_modules/@tryghost/members-api/lib/repositories/member.js:247:29),
    at Function.findPage (/var/lib/ghost/versions/4.12.1/core/server/models/base/plugins/crud.js:73:41),
    at Function.getFilteredCollection (/var/lib/ghost/versions/4.12.1/core/server/models/base/plugins/filtered-collection.js:13:32),
    at Child.applyDefaultAndCustomFilters (/var/lib/ghost/versions/4.12.1/node_modules/@tryghost/bookshelf-filter/lib/bookshelf-filter.js:56:22),
    at Child.query (/var/lib/ghost/versions/4.12.1/node_modules/bookshelf/lib/model.js:1256:22),
    at Object.query (/var/lib/ghost/versions/4.12.1/node_modules/bookshelf/lib/helpers.js:57:14),
    at Builder.<anonymous> (/var/lib/ghost/versions/4.12.1/node_modules/@tryghost/bookshelf-filter/lib/bookshelf-filter.js:63:24),
    at Object.api.parse (/var/lib/ghost/versions/4.12.1/node_modules/@nexes/nql/lib/nql.js:15:31),
    at Object.exports.parse (/var/lib/ghost/versions/4.12.1/node_modules/@nexes/nql-lang/lib/nql.js:18:44),
    at Parser.parse (/var/lib/ghost/versions/4.12.1/node_modules/@nexes/nql-lang/dist/parser.js:245:22),
    at Parser.parser.parseError (/var/lib/ghost/versions/4.12.1/node_modules/@nexes/nql-lang/dist/parser.js:328:12),
Expecting 'LPAREN', 'PROP', got 'LITERAL',
email:james.kerber+test@localhost,
Error: Query Error: unexpected character in filter at char 19,
-------------------^,
    at Immediate.Async.drainQueues [as _onImmediate] (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/async.js:15:14),
    at Async._drainQueues (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/async.js:102:5),
    at _drainQueue (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/async.js:86:9),
    at _drainQueueStep (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/async.js:93:12),
    at Promise._settlePromiseFromHandler (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/promise.js:547:31),
    at Promise._settlePromise (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/promise.js:604:18),
    at Promise._settlePromise0 (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/promise.js:649:10),
    at Promise._settlePromises (/var/lib/ghost/versions/4.12.1/node_modules/bluebird/js/release/promise.js:729:18),
    at Child.applyDefaultAndCustomFilters (/var/lib/ghost/versions/4.12.1/node_modules/@tryghost/bookshelf-filter/lib/bookshelf-filter.js:66:23),
BadRequestError: Error parsing filter,
Error parsing filter,
    f562a280-0c03-11ec-85da-edb292da70de,
Error ID:,
----------------------------------------

This is a huge problem, as it means that certain users will experience usage issues due to their email address just having that character.

To Reproduce

  1. Instantiate GhostAdminAPI with the correct URL and token
  2. Call await ghost.api.members.browse({filter: 'email:'+email}}); with the email variable containing an address like hello+test@localhost

Technical details:

velocity23 commented 3 years ago

Hi there,

I spent some time looking into this and I think I found a solution. The reason Ghost doesn't like the + is because the query language it uses - NQL - sees + as a special character for filtering by something having both (ie looking for posts with both of the tags specified would use tag1+tag2). So to get around this, simply wrap the email filter in quotes like so:

/ghost/api/canary/admin/members/?filter=email%3A'james.kerber%2Btest%40localhost'

Which, when decoded, the filter parameter becomes: email:'james.kerber+test@localhost'

Or, in JS:

await ghost.api.members.browse({filter: `email:'${email}'`}});
matthanley commented 2 years ago

Hey @kerberjg - the comment from @velocity23 above is correct in that the + is a special character in NQL, and you'll need to wrap the email in quotes.