GraphQL access control check failing via local API

default discord avatar
waterlord932 months ago
18

Hello. I am on payload 3.0 beta 55



Worth noting that in this case

payload

object is generated with sanitized config (code for that at the end of the post)



i have following access control in place on collection:


export const adminsAndUsersInSession: Access = async ({ req: { user }, }): Promise<AccessResult> => { if (!user) { return false; } if (checkRole([ERole.admin], user)) { return true; } if (checkRole([ERole.player], user)) { console.log('adminsAndUsersInSession role is player, userId:', user.id); return { players: { contains: user.id, }, }; } return false; };

I get that last console.log with correct userId before getting 403 response




On collection its set up like this


access: { create: anyUser, update: admins, delete: admins, read: adminsAndUsersInSession, // read: () => true, },

but when i do this:


const session = await payload.findByID({ collection: 'game-session', id: sessionId, overrideAccess: false, user: userId, depth: 0, showHiddenFields: false, });

i get 403 fobidden.



However when i target api (from browser window of same user i am passing to findById) for that session id like this: localhost:3000/api/game-session/{sessionId}?depth=0, i get response, which should mean that access control for read works, but for some reason its failing when i am "simulating" user via local-api.



code to get sanitized payload config:


export const payloadWithServerOnlyConfig = async () => { // initiate payload local API const configPath = path.join( process.cwd(), process.env.PAYLOAD_CONFIG_PATH || './src/payload.config.ts', ); const fullConfig = await importWithoutClientFiles<{ default: Promise<SanitizedConfig>; }>(configPath); const config = await fullConfig.default; return await getPayload({ config: config }); };

and then:


const payload = await payloadWithServerOnlyConfig();


GraphQL access control check failing via local API

  • default discord avatar
    markatomniux2 months ago

    What does your game session collection look like?



    Access control is applied to all collections that are referenced as relationships in the target collection. So whatever your access control settings are in game-session is what will be blocking you



    also, use 3 backticks to create a codeblock, and you can add automatic code annotations by using the language file extension netx to the first set of backticks;



    like this '''ts  '''
  • default discord avatar
    waterlord932 months ago

    It has 3 fields, id, players which is relationship hasMany: true of userIds and boardId which is relationship to boards, both of these have basically no access control (just that user must be non-null in req).


    I understand what you are saying and it makes sense, but wouldnt access control on child objects also impact reading when i visit api route for that specific game-session?

  • default discord avatar
    markatomniux2 months ago

    can you please code dump for me?

  • default discord avatar
    waterlord932 months ago

    Sure, give me 5 min do switch to personal pc



    handleChosenSession calls payloadWithServerOnlyConfig and getPlayersInSessionForSocket, the rest of the files are collections and custom access rules i have.



    btw i am using mongoDb cluster with replicaset but only one instance in local, whole project is local only at the moment

  • default discord avatar
    markatomniux2 months ago

    and it's failing specifically on your findById line?

  • default discord avatar
    waterlord932 months ago

    Yes, i get 403 error, if i change that findById to overrideAccess, it works



    Only thing i have not specifically checked is if somehow userId has space or something like that, but in access control method user object with roles exist so i suppose it would have failed that if that was the problem.

  • default discord avatar
    markatomniux2 months ago

    are you sure your attaching your user correctly when this gets called? Is it triggered by an endpoint or a hook?



    if you are checking for a user, but there is no user being parsed by the Payload middleware because it can't find the cookie, that would be a likely cause of failure

  • default discord avatar
    waterlord932 months ago

    I am pretty sure thats not the case, as user object is fine in read access hook, user.roles and user.id both exist, automatically populated... and cookie should not matter, this is local api that i am using...


    this action is triggered when socket.io event happens, that is the reason i am using using config without client dependicies, as i have to register socket events during server boot, and client dependencies were causing me issues - asking for custom loaders for css and scss files etc...

  • default discord avatar
    markatomniux2 months ago

    oh socket io



    so socket io is initiating the payload request?

  • default discord avatar
    waterlord932 months ago

    Yup, when user picks session on the fe, socket.io event is emitted that triggers code in "handleChosenSession.ts"



    Do you think that could be the factor somehow? I cant wrap by head about issue being anything else than graphql return of "adminsAndUsersInSession" not working properly

  • default discord avatar
    markatomniux2 months ago

    I'm don't see any GraphQL anywhere so I don't think that's the issue, and regarding access controls, GraphQL is just querying, access control happens before that.



    I would imagine there's an issue with socket.io. I don't even know how it could work in a 'use server' function, i wouldn't have thought the NextJS app router lifecycle would have allowed for that. Not to mention how expensive that would get running on an edge function.



    I think your socket.io is running in isolation without context of your NextJS app, which is why you're passing the Payload object. That would also indicate to me that your user object will not be getting pulled through either as User is populated in payload middleware (or it WAS populated in middleware, i think it's done in a server action now)



    your user lives in a cookie called payload-token. You might be able to pass that to socket.io. The cookie contains your JWT which payload uses to seed your User object in all contextualised requests and hooks

  • default discord avatar
    waterlord932 months ago

    I am doing that, i am getting userId from that cookie and passing it as userId in payload.findById



    I might be mislabeling the last return of "adminsAndUsersInSession" as "GraphQL" since it looks like graphQL to me ( players: { contains: userId } )

  • default discord avatar
    markatomniux2 months ago

    that's just Payload's query language



    I don't see any cookie code

  • default discord avatar
    waterlord932 months ago

    That happens before any of this code. I didnt consider it relevant since userId i am passing to payload.findById is defined and has a value, and access hook is able to read whole user object

  • default discord avatar
    markatomniux2 months ago

    before your socket.io connection is triggered?



    https://socket.io/docs/v3/handling-cors/

    Try adding credentials include

  • default discord avatar
    waterlord932 months ago

    I read from cookie when connection event happens, and store user id in socket.data.userId, then consume it from there is following socket events

  • default discord avatar
    markatomniux2 months ago

    if you do it with includes, your user ID should get passed to the Payload "middleware"

  • default discord avatar
    waterlord932 months ago

    Ok, will do, still dont see how it would be relevant since payload.findById takes in userId as input and i can guarantee it has correct value at that moment



    Ok, i will give that a try in about 5h from now when i am done with work :)



    i gave it a shot and it absolutely did not change anything :)


    i even tried making PayloadRequest to pass to payload.findById, by using socket.request and socket.request.headers but they are totally incompatible so couldnt do it.



    on the positive note, i just realized that req.user.id in access control hook is actually undefined...



    well i am a fool, i am so sorry for wasting your time, and thank you very much for your help. You did point me in right direction with credentials as it forced me to recheck and add more logs to figure out why user isnt getting there...


    What I didnt realize was that I had another request still being fired from frontend for this session and that was console.log with userId in access control that i was seeing, while one that came from using payload.findById was failing before console.log.



    fix was to fetch whole User object by id, without access control, then pass it to my call in user prop in order to have access control working, just passing id was not enough.

Star on GitHub

Star

Chat on Discord

Discord

online

Can't find what you're looking for?

Get help straight from the Payload team with an Enterprise License.