From 585e4ce14844aa0b6b855fd35581d784c2eafbba Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Thu, 21 Aug 2025 18:08:22 +0200 Subject: [PATCH 1/4] npm: update express to 5.1 #8529 --- src/.claude/settings.json | 15 +- src/packages/hub/package.json | 4 +- src/packages/hub/proxy/index.ts | 14 +- src/packages/hub/servers/app/app-redirect.ts | 2 +- src/packages/hub/servers/app/blobs.ts | 2 +- src/packages/hub/servers/app/next.ts | 14 +- src/packages/next/package.json | 4 +- src/packages/pnpm-lock.yaml | 272 +++++++++++++++++-- src/packages/project/directory-listing.ts | 2 +- src/packages/project/package.json | 4 +- src/packages/server/package.json | 4 +- 11 files changed, 285 insertions(+), 52 deletions(-) diff --git a/src/.claude/settings.json b/src/.claude/settings.json index 5c66a574e5f..92695f80625 100644 --- a/src/.claude/settings.json +++ b/src/.claude/settings.json @@ -19,8 +19,11 @@ "Bash(npx tsc:*)", "Bash(pnpm build:*)", "Bash(pnpm i18n:*)", + "Bash(pnpm list:*)", "Bash(pnpm ts-build:*)", "Bash(pnpm tsc:*)", + "Bash(pnpm view:*)", + "Bash(pnpm why:*)", "Bash(prettier -w:*)", "Bash(psql:*)", "WebFetch(domain:cocalc.com)", @@ -29,11 +32,15 @@ "WebFetch(domain:github.com)", "WebFetch(domain:mistral.ai)", "WebFetch(domain:simplelocalize.io)", - "Bash(pnpm list:*)", - "Bash(pnpm why:*)", - "mcp__github__get_issue", "WebFetch(domain:www.anthropic.com)", - "mcp__cclsp__find_definition" + "mcp__cclsp__find_definition", + "mcp__github__get_issue", + "mcp__github__get_issue_comments", + "mcp__github__get_pull_request", + "mcp__github__get_pull_request_comments", + "mcp__github__get_pull_request_status", + "mcp__github__list_workflow_runs", + "mcp__github__list_workflows" ], "deny": [] } diff --git a/src/packages/hub/package.json b/src/packages/hub/package.json index f7247e7597d..85618989ff2 100644 --- a/src/packages/hub/package.json +++ b/src/packages/hub/package.json @@ -28,7 +28,7 @@ "cookies": "^0.8.0", "cors": "^2.8.5", "debug": "^4.4.0", - "express": "^4.21.2", + "express": "^5.1.0", "formidable": "^3.5.4", "http-proxy-3": "^1.20.8", "lodash": "^4.17.21", @@ -47,7 +47,7 @@ }, "devDependenicesNotes": "For license and size reasons, we make @cocalc/crm a dev dependency so it is NOT installed unless explicitly installed as a separate step.", "devDependencies": { - "@types/express": "^4.17.21", + "@types/express": "^5.0.3", "@types/node": "^18.16.14", "coffeescript": "^2.5.1" }, diff --git a/src/packages/hub/proxy/index.ts b/src/packages/hub/proxy/index.ts index 073fb617c24..193c025dc40 100644 --- a/src/packages/hub/proxy/index.ts +++ b/src/packages/hub/proxy/index.ts @@ -23,8 +23,16 @@ interface Options { export default function initProxy(opts: Options) { const proxy_regexp = `^${ base_path.length <= 1 ? "" : base_path - }\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\/*`; - logger.info("creating proxy server with proxy_regexp", proxy_regexp); + }/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/`; + const proxy_pattern = `${ + base_path.length <= 1 ? "" : base_path + }/:project_id/*splat`; + logger.info( + "creating proxy server with proxy_regexp", + proxy_regexp, + "proxy_pattern", + proxy_pattern, + ); // tcp connections: const handleProxy = initRequest(opts); @@ -32,7 +40,7 @@ export default function initProxy(opts: Options) { // websocket upgrades: const handleUpgrade = initUpgrade(opts, proxy_regexp); - opts.app.all(proxy_regexp, handleProxy); + opts.app.all(proxy_pattern, handleProxy); opts.httpServer.on("upgrade", handleUpgrade); } diff --git a/src/packages/hub/servers/app/app-redirect.ts b/src/packages/hub/servers/app/app-redirect.ts index a9957e2cbce..6b38565b874 100644 --- a/src/packages/hub/servers/app/app-redirect.ts +++ b/src/packages/hub/servers/app/app-redirect.ts @@ -14,7 +14,7 @@ export default function init(router: Router) { const winston = getLogger("app-redirect"); const v: string[] = []; for (const path of APP_ROUTES) { - v.push(`/${path}*`); + v.push(`/${path}/*splat`); } router.get(v, (req, res) => { winston.debug(req.url); diff --git a/src/packages/hub/servers/app/blobs.ts b/src/packages/hub/servers/app/blobs.ts index 204cbdc76ba..7ea93c44d30 100644 --- a/src/packages/hub/servers/app/blobs.ts +++ b/src/packages/hub/servers/app/blobs.ts @@ -8,7 +8,7 @@ import { getLogger } from "@cocalc/hub/logger"; const logger = getLogger("hub:servers:app:blobs"); export default function init(router: Router) { // return uuid-indexed blobs (mainly used for graphics) - router.get("/blobs/*", async (req, res) => { + router.get("/blobs/*splat", async (req, res) => { logger.debug(`${JSON.stringify(req.query)}, ${req.path}`); const uuid = `${req.query.uuid}`; if (req.headers["if-none-match"] === uuid) { diff --git a/src/packages/hub/servers/app/next.ts b/src/packages/hub/servers/app/next.ts index b7233942d80..04fe6097520 100644 --- a/src/packages/hub/servers/app/next.ts +++ b/src/packages/hub/servers/app/next.ts @@ -54,7 +54,7 @@ export default async function init(app: Application) { // 1: The raw static server: const raw = join(shareBasePath, "raw"); app.all( - join(raw, "*"), + join(raw, "*splat"), (req: Request, res: Response, next: NextFunction) => { // Embedding only enabled for PDF files -- see note above const download = @@ -76,7 +76,7 @@ export default async function init(app: Application) { // 2: The download server -- just like raw, but files always get sent via download. const download = join(shareBasePath, "download"); app.all( - join(download, "*"), + join(download, "*splat"), (req: Request, res: Response, next: NextFunction) => { try { handleRaw({ @@ -95,13 +95,15 @@ export default async function init(app: Application) { // 3: Redirects for backward compat; unfortunately there's slight // overhead for doing this on every request. - app.all(join(shareBasePath, "*"), shareRedirect(shareBasePath)); + app.all(join(shareBasePath, "*splat"), shareRedirect(shareBasePath)); } const landingRedirect = createLandingRedirect(); app.all(join(basePath, "index.html"), landingRedirect); - app.all(join(basePath, "doc*"), landingRedirect); - app.all(join(basePath, "policies*"), landingRedirect); + app.all(join(basePath, "doc"), landingRedirect); + app.all(join(basePath, "doc/*splat"), landingRedirect); + app.all(join(basePath, "policies"), landingRedirect); + app.all(join(basePath, "policies/*splat"), landingRedirect); // The next.js server that serves everything else. winston.info( @@ -109,7 +111,7 @@ export default async function init(app: Application) { ); // nextjs listens on everything else - app.all("*", handler); + app.all("*splat", handler); } function parseURL(req: Request, base): { id: string; path: string } { diff --git a/src/packages/next/package.json b/src/packages/next/package.json index 76964040da5..5fdb9619765 100644 --- a/src/packages/next/package.json +++ b/src/packages/next/package.json @@ -75,7 +75,7 @@ "basic-auth": "^2.0.1", "csv-stringify": "^6.3.0", "dayjs": "^1.11.11", - "express": "^4.21.2", + "express": "^5.1.0", "lodash": "^4.17.21", "lru-cache": "^7.18.3", "ms": "2.1.2", @@ -98,7 +98,7 @@ "devDependencies": { "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", - "@types/express": "^4.17.21", + "@types/express": "^5.0.3", "@types/node": "^18.16.14", "@types/react": "^19.1.10", "@uiw/react-textarea-code-editor": "^3.1.1", diff --git a/src/packages/pnpm-lock.yaml b/src/packages/pnpm-lock.yaml index e2de9895410..4a0cb4d46d1 100644 --- a/src/packages/pnpm-lock.yaml +++ b/src/packages/pnpm-lock.yaml @@ -772,8 +772,8 @@ importers: specifier: ^4.4.0 version: 4.4.1 express: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^5.1.0 + version: 5.1.0 formidable: specifier: ^3.5.4 version: 3.5.4 @@ -821,8 +821,8 @@ importers: version: 2.26.1 devDependencies: '@types/express': - specifier: ^4.17.21 - version: 4.17.23 + specifier: ^5.0.3 + version: 5.0.3 '@types/node': specifier: ^18.16.14 version: 18.19.122 @@ -963,8 +963,8 @@ importers: specifier: ^1.11.11 version: 1.11.13 express: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^5.1.0 + version: 5.1.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -1027,8 +1027,8 @@ importers: specifier: ^16.3.0 version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.7(@types/react@19.1.10))(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@types/express': - specifier: ^4.17.21 - version: 4.17.23 + specifier: ^5.0.3 + version: 5.0.3 '@types/node': specifier: ^18.16.14 version: 18.19.122 @@ -1043,7 +1043,7 @@ importers: version: 15.4.6(@swc/helpers@0.5.15)(webpack-hot-middleware@2.26.1) node-mocks-http: specifier: ^1.14.1 - version: 1.17.2(@types/express@4.17.23)(@types/node@18.19.122) + version: 1.17.2(@types/express@5.0.3)(@types/node@18.19.122) project: dependencies: @@ -1105,11 +1105,11 @@ importers: specifier: ^1.1.3 version: 1.2.0 express: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^5.1.0 + version: 5.1.0 express-rate-limit: specifier: ^7.4.0 - version: 7.5.1(express@4.21.2) + version: 7.5.1(express@5.1.0) get-port: specifier: ^5.1.1 version: 5.1.1 @@ -1151,8 +1151,8 @@ importers: specifier: ^1.19.5 version: 1.19.6 '@types/express': - specifier: ^4.17.21 - version: 4.17.23 + specifier: ^5.0.3 + version: 5.0.3 '@types/lodash': specifier: ^4.14.202 version: 4.17.20 @@ -1286,8 +1286,8 @@ importers: specifier: ^2.1.5 version: 2.1.5 express: - specifier: ^4.21.2 - version: 4.21.2 + specifier: ^5.1.0 + version: 5.1.0 express-session: specifier: ^1.18.1 version: 1.18.2 @@ -1401,8 +1401,8 @@ importers: specifier: ^2.1.6 version: 2.1.6 '@types/express': - specifier: ^4.17.21 - version: 4.17.23 + specifier: ^5.0.3 + version: 5.0.3 '@types/express-session': specifier: ^1.18.0 version: 1.18.2 @@ -4301,12 +4301,18 @@ packages: '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + '@types/express-serve-static-core@5.0.7': + resolution: {integrity: sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==} + '@types/express-session@1.18.2': resolution: {integrity: sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==} '@types/express@4.17.23': resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/express@5.0.3': + resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + '@types/formidable@3.4.5': resolution: {integrity: sha512-s7YPsNVfnsng5L8sKnG/Gbb2tiwwJTY1conOkJzTMRvJAlLFW1nEua+ADsJQu8N1c0oTHx9+d5nqg10WuT9gHQ==} @@ -4754,6 +4760,10 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + access-control@1.0.1: resolution: {integrity: sha512-H5aqjkogmFxfaOrfn/e42vyspHVXuJ8er63KuljJXpOyJ1ZO/U5CrHfO8BLKIy2w7mBM02L5quL0vbfQqrGQbA==} @@ -5165,6 +5175,10 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + bonjour-service@1.3.0: resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} @@ -5630,6 +5644,10 @@ packages: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -5647,6 +5665,10 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} @@ -6702,6 +6724,10 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -6810,6 +6836,10 @@ packages: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -6893,6 +6923,10 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -7736,6 +7770,9 @@ packages: resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} engines: {node: '>=0.10.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-redirect@1.0.0: resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==} engines: {node: '>=0.10.0'} @@ -8563,6 +8600,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + memfs@4.17.2: resolution: {integrity: sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==} engines: {node: '>= 4.0.0'} @@ -8584,6 +8625,10 @@ packages: merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge-options@2.0.0: resolution: {integrity: sha512-S7xYIeWHl2ZUKF7SDeBhGg6rfv5bKxVBdk95s/I7wVF8d+hjLSztJ/B271cnUiF6CAFduEQ5Zn3HYwAjT16DlQ==} engines: {node: '>=8'} @@ -8636,6 +8681,10 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -8797,6 +8846,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} @@ -9646,6 +9699,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + raw-loader@0.5.1: resolution: {integrity: sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q==} @@ -10248,6 +10305,10 @@ packages: roughjs@4.6.6: resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + rrweb-cssom@0.8.0: resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} @@ -10376,6 +10437,10 @@ packages: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} @@ -10387,6 +10452,10 @@ packages: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -11111,6 +11180,10 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + type@2.7.3: resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==} @@ -14644,9 +14717,16 @@ snapshots: '@types/range-parser': 1.2.7 '@types/send': 0.17.5 + '@types/express-serve-static-core@5.0.7': + dependencies: + '@types/node': 18.19.122 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + '@types/express-session@1.18.2': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/express@4.17.23': dependencies: @@ -14655,6 +14735,12 @@ snapshots: '@types/qs': 6.14.0 '@types/serve-static': 1.15.8 + '@types/express@5.0.3': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.0.7 + '@types/serve-static': 1.15.8 + '@types/formidable@3.4.5': dependencies: '@types/node': 18.19.122 @@ -14801,24 +14887,24 @@ snapshots: '@types/passport-google-oauth20@2.0.16': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/passport': 1.0.17 '@types/passport-oauth2': 1.8.0 '@types/passport-oauth2@1.8.0': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/oauth': 0.9.6 '@types/passport': 1.0.17 '@types/passport-strategy@0.2.38': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/passport': 1.0.17 '@types/passport@1.0.17': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/pbf@3.0.5': {} @@ -14883,7 +14969,7 @@ snapshots: '@types/serve-index@1.9.4': dependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/serve-static@1.15.8': dependencies: @@ -15174,6 +15260,11 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + access-control@1.0.1: dependencies: millisecond: 0.1.2 @@ -15661,6 +15752,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 @@ -16171,6 +16276,10 @@ snapshots: dependencies: safe-buffer: 5.2.1 + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + content-type@1.0.5: {} convert-source-map@2.0.0: {} @@ -16184,6 +16293,8 @@ snapshots: cookie-signature@1.0.7: {} + cookie-signature@1.2.2: {} + cookie@0.4.2: {} cookie@0.7.1: {} @@ -17459,9 +17570,9 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express-rate-limit@7.5.1(express@4.21.2): + express-rate-limit@7.5.1(express@5.1.0): dependencies: - express: 4.21.2 + express: 5.1.0 express-session@1.18.2: dependencies: @@ -17512,6 +17623,38 @@ snapshots: transitivePeerDependencies: - supports-color + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + exsolve@1.0.7: {} ext@1.7.0: @@ -17620,6 +17763,17 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -17703,6 +17857,8 @@ snapshots: fresh@0.5.2: {} + fresh@2.0.0: {} + from2@2.3.0: dependencies: inherits: 2.0.4 @@ -18722,6 +18878,8 @@ snapshots: is-primitive@3.0.1: {} + is-promise@4.0.0: {} + is-redirect@1.0.0: {} is-regex@1.2.1: @@ -19899,6 +20057,8 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + memfs@4.17.2: dependencies: '@jsonjoy.com/json-pack': 1.2.0(tslib@2.8.1) @@ -19941,6 +20101,8 @@ snapshots: merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} + merge-options@2.0.0: dependencies: is-plain-obj: 2.1.0 @@ -20008,6 +20170,10 @@ snapshots: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mime@3.0.0: {} @@ -20145,6 +20311,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} netmask@2.0.2: {} @@ -20241,7 +20409,7 @@ snapshots: process: 0.11.10 uuid: 9.0.1 - node-mocks-http@1.17.2(@types/express@4.17.23)(@types/node@18.19.122): + node-mocks-http@1.17.2(@types/express@5.0.3)(@types/node@18.19.122): dependencies: accepts: 1.3.8 content-disposition: 0.5.4 @@ -20254,7 +20422,7 @@ snapshots: range-parser: 1.2.1 type-is: 1.6.18 optionalDependencies: - '@types/express': 4.17.23 + '@types/express': 5.0.3 '@types/node': 18.19.122 node-releases@2.0.19: {} @@ -21121,6 +21289,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + raw-loader@0.5.1: {} raw-loader@4.0.2(webpack@5.100.1): @@ -21922,6 +22097,16 @@ snapshots: points-on-curve: 0.2.0 points-on-path: 0.2.1 + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + rrweb-cssom@0.8.0: {} run-applescript@7.0.0: {} @@ -22057,6 +22242,22 @@ snapshots: transitivePeerDependencies: - supports-color + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + serialize-javascript@6.0.2: dependencies: randombytes: 2.1.0 @@ -22082,6 +22283,15 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -22906,6 +23116,12 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + type@2.7.3: {} typed-array-buffer@1.0.3: diff --git a/src/packages/project/directory-listing.ts b/src/packages/project/directory-listing.ts index 77a68bc1fac..e0ac5ed38e5 100644 --- a/src/packages/project/directory-listing.ts +++ b/src/packages/project/directory-listing.ts @@ -24,7 +24,7 @@ export default function init(): Router { const base = "/.smc/directory_listing/"; const router = Router(); - router.get(base + "*", async (req, res) => { + router.get(base + "*splat", async (req, res) => { // decodeURIComponent because decodeURI(misc.encode_path('asdf/te #1/')) != 'asdf/te #1/' // https://github.com/sagemathinc/cocalc/issues/2400 const path = decodeURIComponent(req.path.slice(base.length).trim()); diff --git a/src/packages/project/package.json b/src/packages/project/package.json index bfc3ada64da..fe4591ba603 100644 --- a/src/packages/project/package.json +++ b/src/packages/project/package.json @@ -40,7 +40,7 @@ "compression": "^1.7.4", "daemonize-process": "^3.0.0", "diskusage": "^1.1.3", - "express": "^4.21.2", + "express": "^5.1.0", "express-rate-limit": "^7.4.0", "get-port": "^5.1.1", "lodash": "^4.17.21", @@ -57,7 +57,7 @@ }, "devDependencies": { "@types/body-parser": "^1.19.5", - "@types/express": "^4.17.21", + "@types/express": "^5.0.3", "@types/lodash": "^4.14.202", "@types/node": "^18.16.14", "@types/primus": "^7.3.9", diff --git a/src/packages/server/package.json b/src/packages/server/package.json index 31cb638febe..c0e2c9def5c 100644 --- a/src/packages/server/package.json +++ b/src/packages/server/package.json @@ -81,7 +81,7 @@ "cookies": "^0.8.0", "dayjs": "^1.11.11", "dot-object": "^2.1.5", - "express": "^4.21.2", + "express": "^5.1.0", "express-session": "^1.18.1", "google-auth-library": "^9.4.1", "googleapis": "^137.1.0", @@ -126,7 +126,7 @@ "@types/async": "^2.0.43", "@types/cloudflare": "^2.7.11", "@types/dot-object": "^2.1.6", - "@types/express": "^4.17.21", + "@types/express": "^5.0.3", "@types/express-session": "^1.18.0", "@types/jest": "^29.5.14", "@types/lodash": "^4.14.202", From 1cfe5e8c947aee06a93b7e1f132e87bd880feccc Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 22 Aug 2025 12:08:13 +0200 Subject: [PATCH 2/4] express 5: migrating routes, middleware for project ID regex, but still open issues... --- src/packages/hub/proxy/index.ts | 39 +++++++++++--------- src/packages/hub/servers/app/app-redirect.ts | 6 ++- src/packages/hub/servers/app/next.ts | 12 +++--- src/packages/project/directory-listing.ts | 4 +- src/packages/project/http-api/server.ts | 4 +- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/packages/hub/proxy/index.ts b/src/packages/hub/proxy/index.ts index 193c025dc40..80a6e66f2b3 100644 --- a/src/packages/hub/proxy/index.ts +++ b/src/packages/hub/proxy/index.ts @@ -4,11 +4,12 @@ */ import { Application } from "express"; + +import base_path from "@cocalc/backend/base-path"; +import { ProjectControlFunction } from "@cocalc/server/projects/control"; import getLogger from "../logger"; import initRequest from "./handle-request"; import initUpgrade from "./handle-upgrade"; -import base_path from "@cocalc/backend/base-path"; -import { ProjectControlFunction } from "@cocalc/server/projects/control"; const logger = getLogger("proxy"); @@ -20,27 +21,29 @@ interface Options { proxyConat: boolean; } +const uuidRegex = + /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; + +function uuidMiddleware(req, _res, next) { + if (uuidRegex.test(req.params.project_id)) { + return next(); + } + // Not a valid project ID UUID: skip to next matching route + return next("route"); +} + export default function initProxy(opts: Options) { - const proxy_regexp = `^${ - base_path.length <= 1 ? "" : base_path - }/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/`; - const proxy_pattern = `${ - base_path.length <= 1 ? "" : base_path - }/:project_id/*splat`; - logger.info( - "creating proxy server with proxy_regexp", - proxy_regexp, - "proxy_pattern", - proxy_pattern, - ); - - // tcp connections: + const prefix = base_path.length <= 1 ? "" : base_path; + const routePath = `${prefix}/:project_id/*splat`; + logger.info("creating proxy server for UUIDs only", routePath); + const handleProxy = initRequest(opts); - // websocket upgrades: + const proxy_regexp = `^${prefix}\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$\/(.*)$`; const handleUpgrade = initUpgrade(opts, proxy_regexp); - opts.app.all(proxy_pattern, handleProxy); + // Only handles proxy if the project_id is a valid UUID: + opts.app.all(routePath, uuidMiddleware, handleProxy); opts.httpServer.on("upgrade", handleUpgrade); } diff --git a/src/packages/hub/servers/app/app-redirect.ts b/src/packages/hub/servers/app/app-redirect.ts index 6b38565b874..325a70a91d4 100644 --- a/src/packages/hub/servers/app/app-redirect.ts +++ b/src/packages/hub/servers/app/app-redirect.ts @@ -4,8 +4,9 @@ This redirect is *undone* in @cocalc/frontend/client/handle-hash-url.ts */ -import { join } from "path"; +import { join } from "node:path"; import { Router } from "express"; + import basePath from "@cocalc/backend/base-path"; import { getLogger } from "@cocalc/hub/logger"; import { APP_ROUTES } from "@cocalc/util/routing/app"; @@ -14,7 +15,8 @@ export default function init(router: Router) { const winston = getLogger("app-redirect"); const v: string[] = []; for (const path of APP_ROUTES) { - v.push(`/${path}/*splat`); + v.push(`/${path}`); + v.push(`/${path}/{*splat}`); } router.get(v, (req, res) => { winston.debug(req.url); diff --git a/src/packages/hub/servers/app/next.ts b/src/packages/hub/servers/app/next.ts index 04fe6097520..7de79921b0f 100644 --- a/src/packages/hub/servers/app/next.ts +++ b/src/packages/hub/servers/app/next.ts @@ -11,14 +11,15 @@ import { join } from "path"; // @ts-ignore -- TODO: typescript doesn't like @cocalc/next/init (it is a js file). import initNextServer from "@cocalc/next/init"; + import basePath from "@cocalc/backend/base-path"; import { getLogger } from "@cocalc/hub/logger"; import handleRaw from "@cocalc/next/lib/share/handle-raw"; import { callback2 } from "@cocalc/util/async-utils"; +import { separate_file_extension } from "@cocalc/util/misc"; import { database } from "../database"; import createLandingRedirect from "./landing-redirect"; import shareRedirect from "./share-redirect"; -import { separate_file_extension } from "@cocalc/util/misc"; export default async function init(app: Application) { const winston = getLogger("nextjs"); @@ -95,15 +96,16 @@ export default async function init(app: Application) { // 3: Redirects for backward compat; unfortunately there's slight // overhead for doing this on every request. - app.all(join(shareBasePath, "*splat"), shareRedirect(shareBasePath)); + app.all(join(shareBasePath), shareRedirect(shareBasePath)); + app.all(join(shareBasePath, "{*splat}"), shareRedirect(shareBasePath)); } const landingRedirect = createLandingRedirect(); app.all(join(basePath, "index.html"), landingRedirect); app.all(join(basePath, "doc"), landingRedirect); - app.all(join(basePath, "doc/*splat"), landingRedirect); + app.all(join(basePath, "doc", "{*splat}"), landingRedirect); app.all(join(basePath, "policies"), landingRedirect); - app.all(join(basePath, "policies/*splat"), landingRedirect); + app.all(join(basePath, "policies", "{*splat}"), landingRedirect); // The next.js server that serves everything else. winston.info( @@ -111,7 +113,7 @@ export default async function init(app: Application) { ); // nextjs listens on everything else - app.all("*splat", handler); + app.all("{*splat}", handler); } function parseURL(req: Request, base): { id: string; path: string } { diff --git a/src/packages/project/directory-listing.ts b/src/packages/project/directory-listing.ts index e0ac5ed38e5..5fe9644aa8f 100644 --- a/src/packages/project/directory-listing.ts +++ b/src/packages/project/directory-listing.ts @@ -18,13 +18,15 @@ Browser client code only uses this through the websocket anyways. */ import { Router } from "express"; +import { join } from "node:path"; + import getListing from "@cocalc/backend/get-listing"; export default function init(): Router { const base = "/.smc/directory_listing/"; const router = Router(); - router.get(base + "*splat", async (req, res) => { + router.get(join(base, "{*splat}"), async (req, res) => { // decodeURIComponent because decodeURI(misc.encode_path('asdf/te #1/')) != 'asdf/te #1/' // https://github.com/sagemathinc/cocalc/issues/2400 const path = decodeURIComponent(req.path.slice(base.length).trim()); diff --git a/src/packages/project/http-api/server.ts b/src/packages/project/http-api/server.ts index 1540429e55f..7ed6b5f858a 100644 --- a/src/packages/project/http-api/server.ts +++ b/src/packages/project/http-api/server.ts @@ -74,8 +74,8 @@ function configure(server: express.Application, dbg: Function): void { } }; - server.get("/api/v1/*", handler); - server.post("/api/v1/*", handler); + server.get("/api/v1/{*splat}", handler); + server.post("/api/v1/{*splat}", handler); } function rateLimit(server: express.Application): void { From 11f1509848792fbd1bd8a19effb02ca92fa85a79 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Fri, 29 Aug 2025 15:50:48 +0200 Subject: [PATCH 3/4] hub/express: further tweaks/fixes for express 5.1 --- src/packages/hub/proxy/handle-upgrade.ts | 7 ++++--- src/packages/hub/proxy/index.ts | 20 +++++++++++++------- src/packages/hub/servers/app/blobs.ts | 2 +- src/packages/hub/servers/app/next.ts | 4 ++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/packages/hub/proxy/handle-upgrade.ts b/src/packages/hub/proxy/handle-upgrade.ts index 6d4c5633b94..f6134d9d7ac 100644 --- a/src/packages/hub/proxy/handle-upgrade.ts +++ b/src/packages/hub/proxy/handle-upgrade.ts @@ -2,14 +2,15 @@ import { createProxyServer, type ProxyServer } from "http-proxy-3"; import LRU from "lru-cache"; -import { getEventListeners } from "node:events"; + +import basePath from "@cocalc/backend/base-path"; import getLogger from "@cocalc/hub/logger"; +import { getEventListeners } from "node:events"; +import { proxyConatWebsocket } from "./proxy-conat"; import stripRememberMeCookie from "./strip-remember-me-cookie"; import { getTarget } from "./target"; import { stripBasePath } from "./util"; import { versionCheckFails } from "./version"; -import { proxyConatWebsocket } from "./proxy-conat"; -import basePath from "@cocalc/backend/base-path"; const LISTENERS_HACK = true; diff --git a/src/packages/hub/proxy/index.ts b/src/packages/hub/proxy/index.ts index 80a6e66f2b3..39b0003f735 100644 --- a/src/packages/hub/proxy/index.ts +++ b/src/packages/hub/proxy/index.ts @@ -21,28 +21,34 @@ interface Options { proxyConat: boolean; } -const uuidRegex = +// UUID regex pattern for project ID validation +const UUID_REGEX = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/; +/** + * Middleware to validate that the project_id route parameter is a valid UUID. + * If valid, continues to next middleware. If invalid, skips to next route. + */ function uuidMiddleware(req, _res, next) { - if (uuidRegex.test(req.params.project_id)) { + if (UUID_REGEX.test(req.params.project_id)) { return next(); } - // Not a valid project ID UUID: skip to next matching route + // Not a valid project ID UUID: skip to next route return next("route"); } export default function initProxy(opts: Options) { const prefix = base_path.length <= 1 ? "" : base_path; - const routePath = `${prefix}/:project_id/*splat`; - logger.info("creating proxy server for UUIDs only", routePath); + const routePath = `${prefix}/:project_id/{*splat}`; + logger.info("creating proxy server with route pattern", routePath); const handleProxy = initRequest(opts); - const proxy_regexp = `^${prefix}\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$\/(.*)$`; + // Create regex for upgrade handler (still needed for WebSocket matching) + const proxy_regexp = `^${prefix}\/[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\/.*`; const handleUpgrade = initUpgrade(opts, proxy_regexp); - // Only handles proxy if the project_id is a valid UUID: + // Use Express 5 path syntax with UUID validation middleware opts.app.all(routePath, uuidMiddleware, handleProxy); opts.httpServer.on("upgrade", handleUpgrade); diff --git a/src/packages/hub/servers/app/blobs.ts b/src/packages/hub/servers/app/blobs.ts index 7ea93c44d30..72f10e77ee8 100644 --- a/src/packages/hub/servers/app/blobs.ts +++ b/src/packages/hub/servers/app/blobs.ts @@ -8,7 +8,7 @@ import { getLogger } from "@cocalc/hub/logger"; const logger = getLogger("hub:servers:app:blobs"); export default function init(router: Router) { // return uuid-indexed blobs (mainly used for graphics) - router.get("/blobs/*splat", async (req, res) => { + router.get("/blobs/{*splat}", async (req, res) => { logger.debug(`${JSON.stringify(req.query)}, ${req.path}`); const uuid = `${req.query.uuid}`; if (req.headers["if-none-match"] === uuid) { diff --git a/src/packages/hub/servers/app/next.ts b/src/packages/hub/servers/app/next.ts index 7de79921b0f..5d9c00bf39d 100644 --- a/src/packages/hub/servers/app/next.ts +++ b/src/packages/hub/servers/app/next.ts @@ -55,7 +55,7 @@ export default async function init(app: Application) { // 1: The raw static server: const raw = join(shareBasePath, "raw"); app.all( - join(raw, "*splat"), + join(raw, "{*splat}"), (req: Request, res: Response, next: NextFunction) => { // Embedding only enabled for PDF files -- see note above const download = @@ -77,7 +77,7 @@ export default async function init(app: Application) { // 2: The download server -- just like raw, but files always get sent via download. const download = join(shareBasePath, "download"); app.all( - join(download, "*splat"), + join(download, "{*splat}"), (req: Request, res: Response, next: NextFunction) => { try { handleRaw({ From 6b4d54157024df0907b1ddff83efa8d1ae2cb3d5 Mon Sep 17 00:00:00 2001 From: Harald Schilly Date: Wed, 8 Oct 2025 10:35:33 +0200 Subject: [PATCH 4/4] next api <-> express@5: make the query object writable --- src/packages/frontend/client/api.ts | 2 +- src/packages/next/lib/init.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/packages/frontend/client/api.ts b/src/packages/frontend/client/api.ts index f6edb459fea..44b9983a865 100644 --- a/src/packages/frontend/client/api.ts +++ b/src/packages/frontend/client/api.ts @@ -76,7 +76,7 @@ async function callApi( } if (typeof json == "object" && json.errors) { // This is what happens when the api request fails due to schema validation issues. - // I.e., this is soemthing we only see in dev mode since the schema stuff is disabled in production. + // I.e., this is something we only see in dev mode since the schema stuff is disabled in production. throw Error( `API Schema Error: ${json.message} ${JSON.stringify(json.errors)}`, ); diff --git a/src/packages/next/lib/init.js b/src/packages/next/lib/init.js index 0c2a0ff0639..24406ff8aed 100644 --- a/src/packages/next/lib/init.js +++ b/src/packages/next/lib/init.js @@ -78,6 +78,17 @@ async function init({ basePath }) { winston.info("ready to handle requests:"); return (req, res) => { winston.http(`req.url=${req.url}`); + // Express 5 compatibility: Make req.query writable for Next.js + // Next.js's apiResolver tries to set req.query, but Express 5 makes it read-only + if (req.query !== undefined) { + const queryValue = req.query; + Object.defineProperty(req, "query", { + value: queryValue, + writable: true, + enumerable: true, + configurable: true, + }); + } handle(req, res); }; }