Backend Security, Field Validation, and Timestamps
Please correct me if I’m wrong, but currently there is no way currently to:
- Secure Backend
- Secure createdAt / updatedAt - automatic timestamps
- Field Validation
- Auto edit / generate fields / clear fields
- Complex user validation not having to do with graphql queries directly (@auth rules)
Other than… @lambdas and @custom resolvers.
@custom resolvers will break your subscriptions and require you to fetch an external resource, so that is not a real viable option for backend security.
Future Development:
Although some of this may be fixed as early as this month (March 2021)…
- Custom Timestamps
- Custom Hooks
- Authorization / Validation Rules
Example with @lambda
So, I thought I would post how we can do this with @lambdas in the meantime. I figure this will also serve for other ideas for future dgraph app developers…
First we create the Mutation Schemas
# secure add and update, add createdAt and updatedAt fields
type Post @withSubscription @auth(
add: { rule: "{$DENIED: { eq: \"DENIED\" } }"}
update: { rule: "{$DENIED: { eq: \"DENIED\" } }"}
delete: { rule: "{$ROLE: { eq: \"ADMIN\" } }"}
){
id: ID!
name: String! @search(by: [fulltext])
description: String! @search(by: [fulltext])
nameKebab: String @search(by: [exact])
isPublished: Boolean!
createdAt: DateTime
updatedAt: DateTime
}
# create input fields
input NewPostInput {
name: String!
description: String!
isPublished: Boolean!
}
input EditPostInput {
id: ID!
name: String
description: String
isPublished: Boolean
}
# add new lambda mutations
type Mutation {
newPost(input: NewPostInput!): String @lambda
editPost(input: EditPostInput!): String @lambda
}
Since add and update are taken, I chose new and edit.
newPost
async function newPost({args, dql, authHeader}) {
// get claim data
const headerName = authHeader.key;
const headerValue = authHeader.value;
const [algo, claimsBase64, signature] = headerValue.split(".")
const claims = JSON.parse(atob(claimsBase64));
// verify claims, depends on your setup
...
// claims example
if (claims['ROLE'] === 'ADMIN') {
// run dql query here
}
...
const post = args.input;
// change data example
const nameKebab = post.name.replace(/\s/g, '-').toLowerCase();
// timestamp example
const createdAt = new Date().toISOString();
// validation example
if (post.name.length < 3) {
return 'error-min-length';
}
let newArgs = `
upsert {
query {
q(func: eq(User.email, "${post.email}")) {
v as uid
}
}
mutation {
set {
_:blank-0 <Post.name> "${post.name}" .
_:blank-0 <Post.description> "${post.description}" .
_:blank-0 <Post.nameKebab> "${nameKebab}" .
_:blank-0 <Post.user> uid(v) .
_:blank-0 <Post.isPublished> "${post.isPublished}" .
_:blank-0 <Post.createdAt> "${createdAt}" .
_:blank-0 <dgraph.type> "Post" .
uid(v) <User.posts> _:blank-0 .
}
}
}
`;
const results = await dql.mutate(newArgs);
return results.data.uids['blank-0'];
}
editPost
async function newPost({args, dql, authHeader}) {
// header validation here as well if you need it
...
const post = args.input;
const id = args.input.id
// updatedAt instead of createdAt
const updatedAt = new Date().toISOString();
let newArgs = `
upsert {
query {
q(func: eq(User.email, "${post.email}")) {
v as uid
}
}
mutation {
set {
<${id}> <Post.name> "${post.name}" .
<${id}> <Post.description> "${post.description}" .
<${id}> <Post.nameKebab> "${nameKebab}" .
<${id}> <Post.user> uid(v) .
<${id}> <Post.isPublished> "${post.isPublished}" .
<${id}> <Post.createdAt> "${createdAt}" .
<${id}> <dgraph.type> "Post" .
uid(v) <User.posts> <${id}> .
}
}
}
`;
const results = await dql.mutate(newArgs);
return results.data.code === 'Success'
? id
: null;
}
As you can see, this can get complicated very very quickly, and another reason why we NEED for DQL to accept JSON Format Mutations in lambdas… please add this!
So, I hope this helps someone.
Your Humble Dgraph Newbie,
J