Runtime Permissions in Deno

Deno has been a buzzword since the day it got launched if you haven’t read about it, read here. Deno is always associated with the word Security since the beginning, every article you open about Deno, you will find the word Secure associated with it, and we are glad they chose secure by default.

However, passing the command line argument for taking permissions every time while starting a program is a bad experience for developers who are using a lot of permissions altogether, and is error-prone too.

Android had a similar experience with their earlier versions as Install time request where you cannot install any app before granting all the access, but later that got modernised to Runtime Permissions.

But, what about Deno?

Well, Deno has the permissions API to play around with but is currently unstable, so we have to run our code using —unstable flag for running our code.

Let’s check what Deno Permissions API has to offer

  • request() – Request a new permission

  • revoke() – Revoking a permission

  • query() – Checking the state of existing permission

Querying Permission

Let’s begin by creating one index.ts file for our code, below snippet will write content to a JSON file.

					

import { writeJson } from "https://deno.land/std/fs/mod.ts";
writeJson("/tmp/data.json", { foo: "bar" }, { spaces: 2 }); 

To run the above snippet, we will require write permission.

					

deno run --allow-write index.ts 

However, if we forget to add these permissions in command line argument? It will throw an error.

Permission Error

To handle these errors gracefully, we will use the query() method, that will return the current state of the permission.

Permission state can be of the following

  • granted – Permissions which have been already granted

  • denied – Permissions which have been refused

  • prompt – will prompt the user while requesting for new permissions

Now, we will call the query() method for write permission and check its state.

					

import { writeJson } from "https://deno.land/std/fs/mod.ts";
const permissionAPI = Deno.permissions; // just for shorthand & better readbility

if (writePermission.state === "granted")
  writeJson("/tmp/data.json", { foo: "bar" }, { spaces: 2 });
else console.log("Request write permission before writing to a file");

Finally, use the below command to execute it.

					

deno run --unstable index.ts 

After running this, we will get our customized error message which we have set up.

Runtime Permission

 

Request new permission

Requesting new permission is fairly easy, we have to call request() with the name of the permission.

					

await Deno.permissions.request({ name: "write" }) 

The above snippet will request write permission, and grant our code for writing to files, please also note that await will not throw an error here as Deno comes with Top Level Await

The complete code for requesting permission and writing a file goes like this

					

import { writeJson } from "https://deno.land/std/fs/mod.ts";
const permissionAPI = Deno.permissions; // just for shorthand & better readbility

let writePermission = await permissionAPI.request({ name: "write" }); // query write permission
writeJson("/tmp/data.json", { foo: "bar" }, { spaces: 2 }); // This will write to file

And, to run the above code, we will use the same command.

					

deno run --unstable index.ts 

But instead of an error, this time it will prompt us for granting permission

Deno Grant Permission

This way, we can grant/deny the request at runtime.

Revoke permission

There might be instances where we want to revoke access or maybe allowing access for a certain piece of code, in that case, we can utilize Deno.permissions.revoke() for revoking any granted permissions something like the following snippet.

					

await Deno.permissions.revoke({ name: "write" });

For instance, we want to allow access for writing to specific files, and we can revoke the permissions afterwards.

					

import { writeJson } from "https://deno.land/std/fs/mod.ts";
const permissionAPI = Deno.permissions; // just for shorthand & better readbility

let writePermission = await permissionAPI.request({ name: "write" }); // query write permission

writeJson("/tmp/data.json", { foo: "bar" }, { spaces: 2 }); // This will write to file

await permissionAPI.revoke({ name: "write" }); // revoke write permission

writeJson("/tmp/data_updated.json", { "bar": "foo" }, { spaces: 2 }); // This will write to file

The above snippet will first grant permission to write to a file and later revoke it.And, to run the above code, we will use the same command.

					

deno run --unstable index.ts

After running it, we will first be prompted for permission and succeeding it will throw an error since we revoked permission.

Revoke Permission Deno

Closing Thoughts

Having permissions roles at the runtime is a great addition to your code, in fact, that should be an ideal way of handling your code. But in real instances, your application is far more complex than just writing data to files, for a complex webserver handling lot of network operations, FIO etc can’t have this kind of architecture, eventually will create more problems for granting and revoking permission. The permissions API is in a very early stage and hence it is unstable, we will hear back soon from Deno team on this for a stable and mature version of permissions API.

And we are done 🙂

Hope you enjoyed reading and learned something out of it, feel free to add your questions or thoughts in the comments.