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.