X-Dgraph-AccessToken does not get extracted in lambda

I have modified the original post since I had time to thoroughly test the issue with a local Dgraph environment.

Issue

Although the X-Dgraph-AccessToken is forwarded to the Dgraph lambda server, the token never gets extracted and thus is not available for the built-in wrapper functions

  • graphql
  • dqlQuery
  • dqlMutate

This is critical since if you run Dgraph with ACL and different namespaces, custom lambda resolvers are basically useless, unless you do not use @auth rules (even then it works just by luck).

Environment

  • Dgraph version: v21.03-slash (since it is not an issue of the Dgraph source itself, the issue still persists with later versions)
  • Dgraph Lambda version: latest
  • 2 namespaces

Setup for reproduction

Namespace 0
  • no schema
  • no data apart from ACL data (groot; guardian group)
Namespace 1
Schema
type User {
  id: ID!
  email: String!
}

type Query {
  customUserQuery(id: ID!): User @lambda
}
Lambda
const customUserQuery = async ({args, authHeader, graphql, dql}) => {
   const { id } = args; 

  // Log authHeader
  console.log("Auth Header", authHeader);

  // Query User with GraphQL
  const gql = await graphql(`query User{ getUser(id: ${id}) { email } }`);
  console.log("GraphQL User Query", gql);
  
  // Query User with DQL
  const dqlQ = await dql.query(`{ qUser(func: uid(${id})) { User.email } }`);
  console.log("DQL User Query", dqlQ);

  // Mutate User with DQL
  const dqlM = await dql.mutate({
    set: {
      uid: id,
      "User.email": "new@mail.com"
    }
  });
  console.log("DQL User Mutation", dqlM);

  throw new Error("END Testing");
}

self.addGraphQLResolvers({
  "Query.customUserQuery": customUserQuery,
});

Expected Result

Running the customUserQuery

query CustomUserQuery {
  customUserQuery(id: "0x1") {
    id
  }
}

against the Dgraph endpoint with the X-Dgraph-AccessToken for namespace 1, should result in

GraphQL Query
{
  "data": {
    "customUserQuery": [
      {
        email: "user@mail.com"
      }
    ]
    ...
  }
}
DQL Query
{
  "data": {
    "qUser": [
      {
        "User.email": "user@mail.com"
      }
    ]
  }
}
DQL Muatation
{
  "data": {
    "code": "Success",
    "message": "Done",
    "queries": null,
    "uids": {}
  }
}

Actual Result

Instead we are getting the following errors from the individual requests

GraphQL Query
{
  errors: [
    {
      message: "Not resolving getUser. There's no GraphQL schema in Dgraph. Use the /admin API to add a GraphQL schema"
    }
  ]
}

Explanation: Since there is no X-Dgraph-AccessToken, a GraphQL request always requests from namespace 0.

DQL Query
errors: [
    {
      message: 'rpc error: code = Unauthenticated desc = no accessJwt available'
    }
  ]
}

Explanation: Dgraph alpha expects a X-Dgraph-AccessToken on requests to /mutate or /query endpoints. The Lambda Server does not forward it.

DQL Mutation
errors: [
    {
      message: 'rpc error: code = Unauthenticated desc = no accessJwt available'
    }
  ]
}

Explanation: Dgraph alpha expects a X-Dgraph-AccessToken on requests to /mutate or /query endpoints. The Lambda Server does not forward it.

Solution

Since in the Dgraph alpha source, the X-Dgraph-AccessToken gets forwarded to lambda on a custom request, the token should be available in the authHeader object. Furthermore, this object needs to be attached to ALL requests from lambda to alpha.

Here is the source code line where a possible X-Dgraph-AccessToken header gets lost in lambda.

// possible fix

let authHeader = [b.authHeader];
"X-Dgraph-AccessToken" in b && authHeader.push({ key: "X-Dgraph-AccessToken", value: b["X-Dgraph-AccessToken"]  });

return {
  type: b.resolver,
  parents: b.parents || null,
  args: b.args || {},
  authHeader: authHeader,
  event: b.event || {},
  info: b.info || null,
}

Furthermore, the internal request wrappers (graphql, dqlQuery, dqlMutate), need to be updated to accept a header array

// possible fix (same is applicable to dqlQuery and dqlMutate)

export async function graphql(query: string, variables: Record<string, any> = {}, authHeader?: AuthHeaderField): Promise<GraphQLResponse> {
  
  let headers: Record<string, string> = { "Content-Type": "application/json" };
  if (authHeader) {
    authHeader.forEach(header => {
      headers[header.key]: header.value;
    })
    headers[authHeader.key] = authHeader.value;
  }

  const response = await fetch(`${process.env.DGRAPH_URL}/graphql`, {
    method: "POST",
    headers,
    body: JSON.stringify({ query, variables })
  })
  if (response.status !== 200) {
    throw new Error("Failed to execute GraphQL Query")
  }
  
  return response.json();
} 

Remarks

If this change is not implemented into lambda, the only solution for custom resolvers is to write a custom wrapper for the fetch request and include it in the lambda source. This way the lambda needs to request an X-Dgraph-AccessToken on every request to the lambda. Here is an example for further reading.

X-Dgraph-AccessToken has been exposed in 21.12 and we have not merged this PR yet in 22.0 (to my knowledge).
I created an issue to track this

We will work to get this done asap.
@Poolshark could you add a comment on the issue about the namespace context? We will clarify that too. Thanks.

1 Like

Hi @Raphael, thanks for the reply!

I have reformulated the entire bug report after digging into the code source today… It is definitely an issue. I have also also one opened up on GitHub.

Is the namespace context sufficient now?