edgestorejs/edgestore

Problems to implement file protection functionality.

siqueira-gustavo opened this issue ยท 9 comments

Hi. I'm trying to implement file protection functionality, but I'm having problems with ctx.
It looks like I can't access the context of it.

.path(({ ctx, input }) => [{ type: input.type }, { owner: ctx.userId }])

But when I try to upload files without this context, it works.

.path(({ input }) => [{ type: input.type }])

I really liked edgestore. And I think it will make things much easier. So, I'd like some help.

Here's my code:

import { initEdgeStore } from '@edgestore/server'
import {
  CreateContextOptions,
  createEdgeStoreNextHandler,
} from '@edgestore/server/adapters/next/app'
// import { useSession } from 'next-auth/react'
import { z } from 'zod'

type Context = {
  userId: string
  userRole: 'admin' | 'user'
}

const inputSchema = z.object({
  type: z.enum(['post', 'profile', 'rdo']),
})

function createContext({ req }: CreateContextOptions): Context {
  // const { data } = useSession()

  // if (!data || !data.user) {
  //   return {
  //     userId: '',
  //     userRole: 'user',
  //   }
  // }

  return {
    userId: '123',
    userRole: 'admin',
  }
}

const es = initEdgeStore.context<Context>().create()

const imageBucketConfig = {
  maxSize: 5 * 1024 * 1024,
  accept: ['image/jpeg', 'image/jpg', 'image/png'],
}

function createMetadata(ctx: Context, input: { type: string }) {
  return {
    userRole: ctx.userRole,
    userId: ctx.userId,
    type: input.type,
  }
}

const edgeStoreRouter = es.router({
  publicImages: es
    .imageBucket(imageBucketConfig)
    .input(inputSchema)
    .path(({ ctx, input }) => [{ type: input.type }, { owner: ctx.userId }])
    .metadata(({ ctx, input }) => createMetadata(ctx, input)),

  protectedFiles: es
    .fileBucket(imageBucketConfig)
    .input(inputSchema)
    .path(({ ctx, input }) => [{ type: input.type }, { owner: ctx.userId }])
    .metadata(({ ctx, input }) => createMetadata(ctx, input))
    .accessControl({
      OR: [{ userId: { path: 'owner' } }, { userRole: { eq: 'admin' } }],
    }),
})

const handler = createEdgeStoreNextHandler({
  router: edgeStoreRouter,
  createContext,
})

export { handler as GET, handler as POST }

export type EdgeStoreRouter = typeof edgeStoreRouter

And this is how it is implemented on frontend:

                <MultiFileDropzone
                  className='bg-slate-100 dark:bg-slate-900'
                  value={fileStates}
                  onChange={(files) => {
                    setFileStates(files)
                  }}
                  onFilesAdded={async (addedFiles) => {
                    setFileStates([...fileStates, ...addedFiles])
                    await Promise.all(
                      addedFiles.map(async (addedFileState) => {
                        try {
                          const res = await edgestore.protectedFiles.upload({
                            file: addedFileState.file,
                            options: {
                              temporary: true,
                            },
                            input: { type: 'rdo' },
                            onProgressChange: async (progress: number) => {
                              updateFileProgress(addedFileState.key, progress)
                              if (progress === 100) {
                                // wait 1 second to set it to complete
                                // so that the user can see the progress bar at 100%
                                await new Promise((resolve) =>
                                  setTimeout(resolve, 1000),
                                )
                                updateFileProgress(
                                  addedFileState.key,
                                  'COMPLETE',
                                )
                              }
                            },
                          })
                          setUrls((urls) => [...urls, res.url])
                          console.log(res)
                        } catch (err) {
                          console.log(
                            '๐Ÿš€ ~ file: NewRDOModal.tsx:219 ~ addedFiles.map ~ err:',
                            (err as Error).message,
                          )
                          updateFileProgress(addedFileState.key, 'ERROR')
                        }
                      }),
                    )
                  }}
                />

Thanks in advance.

@siqueira-gustavo Thank you for creating the issue and for using edgestore!
I couldn't find any problems with the code just by looking at it.
I'll try it out and report back as soon as I can.

@siqueira-gustavo I just tried your router configuration in an example app and it worked as expected.
I pushed the changes into the tmp/issue-10 branch here:
4f9a16c

If you want to try to run this example, you can do it following these steps:

if the example app works in your machine, we would need to try to find the difference in the project that's making it fail.

Thanks for your reply!
I followed your instructions, but I'm still having problems... starting to think it's something here with my configs...

I made a video...
https://github.com/edgestorejs/edgestore/assets/69218857/96a2a1cc-39a0-490f-822f-c2a829dda967

@siqueira-gustavo strange.. I think I need to take a look in the network tab. and also see the server side logs to understand what's going on..
maybe it would be better if you could share your screen on Discord to make it easier to debug.
If you're open to it, join the Discord server and DM me so we can find a time that can fit both our schedules.

@perfectbase yeah... really strange.
I have a Linux machine I'll try to setup to test it in there when I have time.
Now for some reason it is sending a response to the (protected) bucket, but it is pointing to localhost instead of the accessUrl...
I made another video to show it.

edgestrore-problems.mp4

Not sure if the quality is good, because max size to upload videos here is only 10MB.

Btw, sessions userId should've be my name, not '123' as it shown in the network tab.

image

image

image

image

And if you try to click the link, it's just a plain text.
I'll try to reach you on Discord.

@siqueira-gustavo Oh! Now I think I understand what's happening.
The upload is successful, but you can't show the image on the screen right!? I think it's because you are trying to use next/image to show it, but it doesn't forward the cookies.

I believe all you need to know is in this "Good to know" section:

image

Tell me if it helps.

@perfectbase you're right! It's working! Thanks a lot!

edgestrore-solution.mp4

I actually read this part of documentation, but not so carefully as I should... ๐Ÿ˜…
I only have one more question: in public files table we have links to access the uploaded files but in protected table there are no links. Is it the expected behavior?

@siqueira-gustavo Nice! Glad that it worked!

Yes, this is the correct behavior right now. The reason is that even if I put the links there, you would only see "access denied" because you don't have the cookies to access the file.

Eventually I plan to improve this page so that you can see more details and maybe access the file with a signed url (that doesn't need the cookies)

@perfectbase Oh, I see... I really liked this project!
Maybe I'll be able to contribute in a near future. ๐Ÿ˜๐Ÿ‘
Thanks again!
I'm closing this issue now. See you!