tus / tus-node-server

Node.js tus server, standalone or integrable in any framework, with disk, S3, Azure, and GGC stores.
https://tus.io/
MIT License
814 stars 200 forks source link

`generateUrl` returns insecure URL and subsequent chunk uploads fail #635

Closed mfts closed 3 months ago

mfts commented 3 months ago

Initial checklist

Steps to reproduce

I'm using @tus/server with next.js pages router.

I set up my api route: pages/api/file/tus/[[...file]].ts

import type { NextApiRequest, NextApiResponse } from "next";

import slugify from "@sindresorhus/slugify";
import { S3Store } from "@tus/s3-store";
import { Server } from "@tus/server";
import { getServerSession } from "next-auth/next";
import path from "node:path";

import { newId } from "@/lib/id-helper";

import { authOptions } from "../../auth/[...nextauth]";

export const config = {
  api: {
    bodyParser: false,
  },
};

const tusServer = new Server({
  // `path` needs to match the route declared by the next file router
  path: "/api/file/tus",
  datastore: new S3Store({
    partSize: 8 * 1024 * 1024, // each uploaded part will have ~8MiB,
    s3ClientConfig: {
      bucket: process.env.NEXT_PRIVATE_UPLOAD_BUCKET as string,
      region: process.env.NEXT_PRIVATE_UPLOAD_REGION as string,
      credentials: {
        accessKeyId: process.env.NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID as string,
        secretAccessKey: process.env
          .NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY as string,
      },
    },
  }),
  namingFunction(req, metadata) {
    const { teamId, fileName } = metadata as {
      teamId: string;
      fileName: string;
    };
    const docId = newId("doc");
    const { name, ext } = path.parse(fileName);
    const newName = `${teamId}/${docId}/${slugify(name)}${ext}`;
    return newName;
  },
  generateUrl(req, { proto, host, path, id }) {
    // Encode the ID to be URL safe
    id = Buffer.from(id, "utf-8").toString("base64url");
    return `${proto}://${host}${path}/${id}`;
  },
  getFileIdFromRequest(req) {
    // Extract the ID from the URL
    const id = (req.url as string).split("/api/file/tus/")[1];
    return Buffer.from(id, "base64url").toString("utf-8");
  },
});

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  // Get the session
  const session = getServerSession(req, res, authOptions);
  if (!session) {
    return res.status(401).json({ message: "Unauthorized" });
  }

  return tusServer.handle(req, res);
}

Everything works well if the file is less than the chunk size I specified in the tus.Upload from tus-js-client implementation. However, if that exceeds then @tus/server generates a new URL for subsequent chunk uploads using the generateUrl function. For whatever reason, the proto value returns http instead of https. This leads to an insecure URL and my application refuses to execute it, throwing CORS errors.


I fixed it by modifying the return statement of generateUrl and hardcoding a s after proto.

...
generateUrl(req, { proto, host, path, id }) {
    // Encode the ID to be URL safe
    id = Buffer.from(id, "utf-8").toString("base64url");
    return `${proto}s://${host}${path}/${id}`;
  },
...

Expected behavior

proto should be https not http

Actual behavior

proto is http not https

Murderlon commented 3 months ago

Hi, you checked the checkbox indicating you searched issues and find nothing relevant. The most recent issue that was openend before yours, with almost the same title, has your answer: https://github.com/tus/tus-node-server/issues/634 (which in turn was a duplicate of another of the same).

Regarding chunkSize, you should never set it on the client unless you are forced too. See the warnings in the tus-js-client docs if you're wondering why.

mfts commented 3 months ago

Thanks @Murderlon. I swear I searched the issues but must have forgotten to look in closed issues. My bad.

Regarding chunkSize, you should never set it on the client unless you are forced too. See the warnings in the tus-js-client docs if you're wondering why.

Yes, that's a great point. I'm deploying to a serverless infra, therefore my request body size is limited to 4.5 MB hence the chunkSize in the tus-js-client.