Permission denied although privilege elevation

I have the following function which runs with the server role:

@role(server)
function signUpWithSocialProvider(sp: {email: String, userId: String, provider: "Github" | "Facebook" | "Passkey" | "Google" , firstName: String, lastName: String, image: String | Null}): { user: User, tokens: { access: Token, refresh: Token }} {
    
  createSocialProviderAccount(sp.firstName, sp.lastName, sp.email, sp.image, sp.provider, sp.userId)
  let tokens = signInWithSocialProvider(sp.provider, sp.userId)
  {
    user: User,
    tokens: tokens
  }
}

It will be called with a Key having the following role:

role role_signIn {
  privileges signUpWithSocialProvider {
    call
  }
  privileges signInWithSocialProvider {
    call
  }
}

Still I’m getting a permission denied for the User collection as part of the response:

{
  user: User("416801358062027979") /* permission denied */,
  tokens: {
    access: {
      id: "416871350343303371",
      coll: Token,
      ts: Time("2024-12-10T08:12:30.100Z"),
      ttl: Time("2024-12-10T08:22:29.649157Z"),
      secret: "fn...",
      document: User("416801358062027979") /* permission denied */,
      data: {
        type: "access",
        refresh: Token("416871350208037067")
      }
    },
    refresh: {
      id: "416871350208037067",
      coll: Token,
      ts: Time("2024-12-10T08:12:30.100Z"),
      ttl: Time("2024-12-10T16:12:29.649157Z"),
      data: {
        type: "refresh"
      },
      secret: "fn...",
      document: User("416801358062027979") /* permission denied */
    }
  }
}

Given that the function runs with server role I would expect to not see this permission denied error :thinking:.

The permission-denied errors are due to when/where materializing documents happens: Fauna does not materialize references into documents until they are either:

  • explicitly read from (e.g. using dot syntax or projection)
  • the query is returned to the user, at which point Fauna materializes all the reference in the response

Right now, your UDF is returning a User ref, which the caller does not have permission to read and materialize. Same with the token.document fields.

If you want the UDF to have read privileges, but not the caller, then make sure you materialize what you want inside of the UDF before returning. That is, convert the ref into an object, which is likely easiest using projection.

@role(server)
function signUpWithSocialProvider(sp: {email: String, userId: String, provider: "Github" | "Facebook" | "Passkey" | "Google" , firstName: String, lastName: String, image: String | Null}): { user: User, tokens: { access: Token, refresh: Token }} {
    
  createSocialProviderAccount(sp.firstName, sp.lastName, sp.email, sp.image, sp.provider, sp.userId)
  let tokens = signInWithSocialProvider(sp.provider, sp.userId)
  {
    // use projection to return a plain object
    user: User { id, ts },
    tokens: tokens { id, ts, secret, data }
  }
}

The fact that References and Documents act interchangeably within FQL most of the time is not well documented. I’ve taken this back to the team to see what we can do to make this more clear and searchable.