ADR 003: Permissions - Better Safe Than Sorry

The common problem with any app is checking permissions. The most obvious approach, especially with a framework as simple as Gin is to check permissions manually in every controller. The obvious pitfall is that one may simply forget to add the required code.

The less obvious, but also common problem is that now you need to keep and maintain the list of permissions and decide how each permission corresponds to each endpoint.

The approach in cuento is somewhat simplified and very straightforward: each endpoint has its own permission, and the name of that permission is the same as the path to that endpoint (we assume that we will never have two endpoints with the same path). The permissions are not stored anywhere in the code separately, instead they are implied.

A special middleware checks the database to find a record “user role - permission.” If this record is present in the table, the access is granted. If not, the API returns an error. To make the errors meaningful and not simply return something like “access to api/episode/create” is denied, we have made a wrapper over the standard router, and now each rout in the code has a human-readable description. So the error looks like “User does not have access to episode creation.”

There is also a special API epidpoint which cah return all the existing permissions together with their descriptions. It is autogenerated from the registered endpoints in the router and does not require human maintenance. This list can then be used to give (or revoke) specific permissions to specific roles.

Of course, there are situations where we need to go even more granular than that — when the permission depends not only on the endpoint, but also on the data itself. For example, different subforums may have their own visibility permissions. Here, we cannot avoid adding the login into the controller, and also these cases will not be reflected in the autogenerated permission list.

However, on further reflection, those cases do not match the standard permission schema anyway. They should have their own config stored separately and not be mixed with the generic broad permissions.