Mercury Retrograde: Access Control with a Planetary Twist
Mercury going retrograde is a serious affair, at least according to Reddit discussions. Mercury being the God of communication, supposedly, its (apparent) retrograde movement causes communication breakdowns leading to network outages, server crashes, data loss, and so on. Some take it quite seriously. There is even a Zapier integration to notify you in Slack when Mercury is about to retrograde. Well, it is about to happen tomorrow! So let me hurry up and publish this post before it is too late 😄.
Astronomically, it is an interesting phenomenon, quite nicely visualized here.
With a good explainer here.
In the previous blog, we dealt with access control to express the ownership relation: a todo must be accessible to only those who own it, with some role-based exceptions. However, sometimes you need to go beyond role-based and ownership-based rules. Rather than supporting specific authorization policies that eventually prove too limiting, Exograph takes a different approach through its context mechanism. This mechanism opens the door to implementing even esoteric rules, such as "no deployment when Mercury is retrograde".
Setting up the context
Implementing such a rule in Exograph requires introducing a new context whose field represents Mercury's retrograde status. In Exograph, a context represents information extracted from the incoming request (headers, cookies, IP address) and the environment variables. Exograph includes a general mechanism where users can write custom code to populate the context. That is what we will use to implement the Mercury retrograde rule.
To implement custom context extraction logic, you need to:
- Write a GraphQL query to return the computed value of interest (in our case, whether Mercury is retrograde)
- Use the
@query
annotation to bind the value of the query to a context field (in our case, a boolean field)
Let's start by writing TypeScript code to determine if Mercury is retrograde. We will use https://mercuryretrogradeapi.com/ for that (quite an interesting service deployed to 5+ geographically distributed servers!).
export function isMercuryRetrograde(): bool {
let result = await fetch("https://mercuryretrogradeapi.com");
let json = await result.json();
return json.is_retrograde;
}
Then, we need to bind this query to a context field. We will use the @query
annotation to do that.
@deno("astro.ts")
module AstroModule {
query isMercuryRetrograde(): bool
}
context AstroContext {
@query("isMercuryRetrograde") mercuryRetrograde: bool
}
Here, the AstroModule
exposes a single isMercuryRetrograde
query that returns a boolean value. We use this query to populate the mercuryRetrograde
context variable.
Expressing access control rules
Finally, let's write an example resource—Deployment
—that we want to protect against mercury's shenanigans. We do now want any mutations to Deployment
when Mercury is retrograde.
@postgres
module DeploymentModule {
@access(query = true, mutation = !AstroContext.mercuryRetrograde)
type Deployment {
@pk id: Int = autoIncrement()
version: String
}
}
Play along by cloning the examples repository.
Here, we allow anyone to query the deployment. But we disallow mutations when Mercury is retrograde (you could combine this with other rules, for example, to restrict mutations to admin users only).
Testing the rule
Let's start the server:
exo yolo
You will see output similar to this:
...
Started server on localhost:9876 in 5.35 ms
- Playground hosted at:
http://localhost:9876/playground
- Endpoint hosted at:
http://localhost:9876/graphql
Visit the playground URL and try to create a new deployment:
mutation {
createDeployment(data: { version: "1" }) {
id
}
}
If you try it while Mercury is not retrograde, it will succeed.
{
"data": {
"createDeployment": {
"id": 1
}
}
}
But if you try it while Mercury is retrograde (the next such thing will happen between August 23, 2023 and September 14, 2023), you will get an error:
{
"errors": [
{
"message": "Not authorized"
}
]
}
The same thing will happen if you try to update or delete the deployment:
mutation {
updateDeployment(id: 1, data: { version: "2" }) {
id
version
}
}
mutation {
deleteDeployment(id: 1) {
id
}
}
There is no escaping the access rule!
Conclusion
This fun little exercise shows how Exograph can be used to implement flexible access control rules. Besides built-in context sources, such as the JWT token, header, cookie, and environment variable, Exograph allows sourcing rules from a query implemented using Typescript/JavaScript running in Deno. This separation of context extraction logic from access control expressions enables easy implementation of even weird rules in Exograph.
What's next? Does someone want to implement "No Friday the 13th" rule? Or "No full moon" rule? Or "No deployment on the day of the week that ends with 'y'" rule? Exograph got you covered!