adminjs icon indicating copy to clipboard operation
adminjs copied to clipboard

PKs with slashes break some functionality

Open fabiosantoscode opened this issue 1 year ago • 0 comments

Describe the bug

Some primary keys in a project I work on have slash characters, and some activities such as attempting to visit their "Edit" page, or selecting them from a dropdown, are broken.

This seems to be caused by URL concatenation, because the / character is interpreted as an URL separator.

Installed libraries and their versions

    "@adminjs/express": "^5.0.1",
    "@adminjs/sequelize": "^3.0.0",
    "adminjs": "^6.7.1",

To Reproduce Steps to reproduce the behavior:

  1. Create a table which has a string as a primary key, and set it up with adminjs
  2. Create a record where this primary key has a slash, for instance part 1/2.
  3. Visit the edit page for that record
  4. Notice the error
  5. Observe the URL is incorrect /admin/resources/level/records/part 1/2/show due to a slash

Expected behavior

URLs containing record IDs should have slashes escaped. In the above example, /admin/resources/level/records/part 1%2F2/show

Additional context

I was able to work around this issue using patch-package to patch up some files. Here is an abridged version that includes only the source .ts files.

diff --git a/node_modules/adminjs/src/backend/utils/view-helpers/view-helpers.ts b/node_modules/adminjs/src/backend/utils/view-helpers/view-helpers.ts
index 71090dc..746ec85 100644
--- a/node_modules/adminjs/src/backend/utils/view-helpers/view-helpers.ts
+++ b/node_modules/adminjs/src/backend/utils/view-helpers/view-helpers.ts
@@ -103,7 +103,7 @@ export class ViewHelpers {
     let { rootPath } = this.options
     if (!rootPath.startsWith(separator)) { rootPath = `${separator}${rootPath}` }
 
-    const parts = [rootPath, ...paths]
+    const parts = [rootPath, ...paths.map(s => typeof s === 'string' ? s.replace(/\//g, '%2F') : s)]
     return `${parts.join(separator).replace(replace, separator)}${search}`
   }
 
diff --git a/node_modules/adminjs/src/frontend/utils/api-client.ts b/node_modules/adminjs/src/frontend/utils/api-client.ts
index a8afeb2..43fda23 100644
--- a/node_modules/adminjs/src/frontend/utils/api-client.ts
+++ b/node_modules/adminjs/src/frontend/utils/api-client.ts
@@ -190,7 +190,7 @@ class ApiClient {
   async recordAction(options: RecordActionAPIParams): Promise<AxiosResponse<RecordActionResponse>> {
     const { resourceId, recordId, actionName, data, ...axiosParams } = options
     const response = await this.client.request({
-      url: `/api/resources/${resourceId}/records/${recordId}/${actionName}`,
+      url: `/api/resources/${resourceId}/records/${typeof recordId !== 'string' ? recordId : recordId.replace('/', '%2F')}/${actionName}`,
       method: data ? 'POST' : 'GET',
       ...axiosParams,
       data,

fabiosantoscode avatar Dec 14 '22 22:12 fabiosantoscode