NotionX / react-notion-x

Fast and accurate React renderer for Notion. TS batteries included. ⚡️
https://react-notion-x-demo.transitivebullsh.it
MIT License
4.75k stars 552 forks source link

Images from Signed URL returns 403 #476

Open maxswjeon opened 1 year ago

maxswjeon commented 1 year ago

Description

Images accessed with signed url results in 403 now.

Notion Test Page ID

Currently using the function below.

function getPageCover(
  pageData: ExtendedRecordMap,
  pageBlock: PageBlock
): string | null {
  const cover = pageBlock.format?.page_cover;

  if (cover) {
    if (!cover.startsWith("http")) {
      return `https://www.notion.so${cover}`;
    }

    return pageData.signed_urls[pageBlock.id];
  }

  const images = getDirectChild(pageData, "image") as ImageBlock[];
  if (images.length === 0) {
    return null;
  }
  const image = images[0];

  return pageData.signed_urls[image.id];
}

It returns signed url, such as https://file.notion.so/f/s/cceaadce-b934-4469-a976-1639abc31c16/cover_tech_blog.png?id=cdce9a22-28db-4759-b55b-3eca64d17d6d&table=block&spaceId=9b56c2e3-24a2-45c2-b3f7-92eb37529384&expirationTimestamp=1682528539982&signature=TaFx_XF7gC__IrWAbK4kn8Sf8yiT9g5IirYlzCKuEPQ

However, I cannot access those images on incognito mode.

I can provide more URLs when the above URL is expired. Please check this issue.

NyaMisty commented 1 year ago

It seems that for file.notion.so files, you will need a "file_token" cookie entry.

Note1: it's generated by server and sent to client in https://www.notion.so/api/v3/loginWithEmail API's Set-Cookie header Note2: so, that means that with this token one can access all images (but without token_v2, one can't see the reference to the image)

maxswjeon commented 1 year ago

Signed URL should be accessible without authentication / authorization and cookies. Might have to find another solution, such as caching image on the server...

NyaMisty commented 1 year ago

@maxswjeon Yup, Signed URL SHOULD be accessible without auth, and that's exactly the old situation. But for unknown reason, Notion changed the policy, and the signed URL now requires authenticaiton to be used.

For image, the url is like: https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F62b2307c-2989-4b2d-8d4c-f5afdde81aa8%2F204E0CCA-43E8-4EEF-8532-CEAF58985929.png?table=block&id=903b1249-0721-45b3-aff3-8248841429d2&cache=v2 , it will now require token_v2 cookie.

For original image or file assets, the url is like: https://file.notion.so/f/s/ece240be-7458-4dd3-b254-f2b849e6c1a5/themebg.zip?id=cd5d4330-8f2a-4361-a9ef-043f466e8135&table=block&spaceId=c8569e32-89f8-4ddf-ac21-adb5a19fdb24&expirationTimestamp=1683967533433&signature=mp1tm-3OotSQnfPSbROp7A3Hi_E3S-m3eWlVfjdXGyc, and it will now require file_token cookie.

To work around this, I made a simple CloudFlare Worker script, to proxy these requests with corresponding cookie. To use this script, you will need to create a worker and setup the TOKEN_V2 and FILE_TOKEN variable in Settings -> Variables -> Environment Variables.

export default {
  async fetch(request, env) {
    try {
      console.log(env)
      const reqURL = new URL(request.url);
      const oriURL = "https://" + reqURL.pathname.substr(1) + reqURL.search
      console.log(reqURL.pathname)
      console.log(oriURL)
      if (reqURL.pathname.startsWith("/www.notion.so/image")) {
        return fetch(oriURL, {
          headers: {
            "Cookie": "token_v2=" + env.TOKEN_V2,
          }
        })
        // .then(() => { }).catch((e) => console.log(e))
        // return new Response("URL: " + oriURL, { status: 500 })
      } else if (reqURL.pathname.startsWith("/file.notion.so")) {
        return fetch(oriURL, {
          headers: {
            "Cookie": "file_token=" + env.FILE_TOKEN,
          }
        })
      }

      return new Response("Invalid URL: " + oriURL, { status: 400 })
    } catch(err) {
      return new Response(err.stack, { status: 500 })
    }
  }
}

The usage is replace https:// with your worker domain (e.g. https://notion-image-proxy.misty.workers.dev/)

An example url is https://notion-image-proxy.misty.workers.dev/www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F62b2307c-2989-4b2d-8d4c-f5afdde81aa8%2F204E0CCA-43E8-4EEF-8532-CEAF58985929.png?table=block&id=903b1249-0721-45b3-aff3-8248841429d2&cache=v2

drewatdrawn commented 1 year ago

@NyaMisty I like the proxy method. Do you know if this something I could do with Netlify functions? I don't think I'm fully grasping the solution you have here.

NyaMisty commented 1 year ago

@NyaMisty I like the proxy method. Do you know if this something I could do with Netlify functions? I don't think I'm fully grasping the solution you have here.

Should be the same, you have to adapt my script into Netlify function, they are basically the same You have to change function definition & return value

exports.handler = async (event, context) => {
    return { statusCode: 200, body: JSON.stringify({ data }) };

However I'm not familiar with Netlify, you have to work your way out :(

drewatdrawn commented 1 year ago

I think the thing that was hanging me up was how you were changing the source URL, but I found that example in your nextjs-notion repo and was able to set up the remapping and redirect. Still working on getting the returned image from the aws server but the Netlify redirect function is working.