diff --git a/packages/worker/src/api/controllers/global/users.ts b/packages/worker/src/api/controllers/global/users.ts index 58979ec799..1c470bdba9 100644 --- a/packages/worker/src/api/controllers/global/users.ts +++ b/packages/worker/src/api/controllers/global/users.ts @@ -14,6 +14,7 @@ import { InviteUsersResponse, MigrationType, SaveUserResponse, + SearchQueryOperators, SearchUsersRequest, User, UserCtx, @@ -29,6 +30,7 @@ import { } from "@budibase/backend-core" import { checkAnyUserExists } from "../../../utilities/users" import { isEmailConfigured } from "../../../utilities/email" +import { removeKeyNumbering } from "@budibase/backend-core/src/db" const MAX_USERS_UPLOAD_LIMIT = 1000 @@ -185,9 +187,23 @@ export const getAppUsers = async (ctx: Ctx) => { export const search = async (ctx: Ctx) => { const body = ctx.request.body - // TODO: for now only one supported search key, string.email - if (body?.query && !userSdk.core.isSupportedUserSearch(body.query)) { - ctx.throw(501, "Can only search by string.email or equal._id") + // TODO: for now only two supported search keys; string.email and equal._id + if (body?.query) { + // Clean numeric prefixing. This will overwrite duplicate search fields, + // but this is fine because we only support a single custom search on + // email and id + for (let filters of Object.values(body.query)) { + if (filters && typeof filters === "object") { + for (let [field, value] of Object.entries(filters)) { + delete filters[field] + filters[removeKeyNumbering(field)] = value + } + } + } + // Validate we aren't trying to search on any illegal fields + if (!userSdk.core.isSupportedUserSearch(body.query)) { + ctx.throw(501, "Can only search by string.email or equal._id") + } } if (body.paginate === false) { diff --git a/packages/worker/src/api/routes/global/tests/users.spec.ts b/packages/worker/src/api/routes/global/tests/users.spec.ts index a85933255a..cb534a770a 100644 --- a/packages/worker/src/api/routes/global/tests/users.spec.ts +++ b/packages/worker/src/api/routes/global/tests/users.spec.ts @@ -590,6 +590,15 @@ describe("/api/global/users", () => { expect(response.body.data[0].email).toBe(user.email) }) + it("should be able to search by email with numeric prefixing", async () => { + const user = await config.createUser() + const response = await config.api.users.searchUsers({ + query: { string: { ["999:email"]: user.email } }, + }) + expect(response.body.data.length).toBe(1) + expect(response.body.data[0].email).toBe(user.email) + }) + it("should be able to search by _id", async () => { const user = await config.createUser() const response = await config.api.users.searchUsers({ @@ -599,6 +608,15 @@ describe("/api/global/users", () => { expect(response.body.data[0]._id).toBe(user._id) }) + it("should be able to search by _id with numeric prefixing", async () => { + const user = await config.createUser() + const response = await config.api.users.searchUsers({ + query: { equal: { ["1:_id"]: user._id } }, + }) + expect(response.body.data.length).toBe(1) + expect(response.body.data[0]._id).toBe(user._id) + }) + it("should throw an error when unimplemented options used", async () => { const user = await config.createUser() await config.api.users.searchUsers(