Dejan Lukić

Oso + PlanetScale

Originally written for Oso.

PlanetScale is a database platform designed to facilitate the management and scaling of distributed databases.

With a focus on simplicity and efficiency, PlanetScale aims to streamline the challenges of handling databases that span multiple regions.

By offering tools and features that prioritize data integrity and availability, PlanetScale provides developers with a practical solution for building and maintaining databases that can seamlessly operate in various geographic locations

PlanetScale does not inherently provide support for Role-Based Access Control (RBAC) or Attribute-Based Access Control (ABAC) as part of its default functionality.

RBAC establishes a framework for overseeing and implementing user permissions, contingent on the roles they hold within an organization. RBAC guarantees that users are restricted from accessing resources and executing actions pertinent to their designated roles.

ABAC operates comparably, enforcing user permissions by considering attributes such as user traits, properties of resources, and contextual circumstances. This approach permits a higher degree of precision when regulating access.

To implement RBAC or ABAC with PlanetScale in this guide, you will use Oso - a batteries-included authorization-as-a-service (AaaS) provider.

Prerequisites

Setting Up the Node.js Project

You will use Prisma - an Object Relational Mapping (ORM) library for this project.

Head over to your terminal and install the necessary dependencies:

npm i --save-dev prisma

This will install the Prisma CLI, necessary for pushing changes from schema to the database.

You will also need clients for Prisma and Oso.

npm i @prisma/client oso-cloud

Follow by initializing a Prisma project:

npx prisma init

Create a new file index.js which will serve as the main file in the project.

Continue by importing the dependencies:

import { PrismaClient } from '@prisma/client';
import { Oso } from 'oso-cloud';

Next, add Oso’s API key:

const osoApiKey = process.env.OSO_API_KEY || '';

Note: These should be read from the environment variables. For that instance use the dotenv package.

Setting Up Oso Rules

Next, you'll establish Oso Rules that will define which roles have permission to perform specific actions on arbitrary resources.

Access the Oso Rules Editor in the Workbench mode - a more visually pleasing way of implementing auhtorization.

Oso Rules Editor

In the top section, it should say Add a resource. Next to the dropdown, enter the Resource name Star. Press the + button on the right side to create a new resource.

Added resource in Oso Rules Editor

Resource Star will now be populated with the default roles and permissions. Since we will dynamically provide Oso with actions performed by an actor, they should match action names from Prisma.

For example, CRUD operations in Prisma can be used.

Next, change the view permission to findMany and edit to create.

Permission showcase in Oso Rules Editor

You can also create tests, automatically, by pressing Create Test. This will create a few edge-cases for you to visually see if the tests have passed.

Test automation in Oso Dashboard

All of this resulted in the following Polar policy.

Polar is a declarative policy language used for defining authorization rules in applications.

actor User {}

resource Star {
  roles = ["viewer", "owner"];
  permissions = ["findMany", "create"];

  "findMany" if "viewer";
  "create" if "owner";
  "viewer" if "owner";
}

test "Star roles and permissions" {
  setup {
    has_role(User{"alice"}, "viewer", Star{"example"});
    has_role(User{"bob"}, "owner", Star{"example"});
  }
  assert     allow(User{"alice"}, "findMany", Star{"example"});
  assert     allow(User{"bob"}, "findMany", Star{"example"});
  assert_not allow(User{"alice"}, "create", Star{"example"});
  assert     allow(User{"bob"}, "create", Star{"example"});
}

Integrating Oso in Prisma

You will now create a custom Prisma Client query, that will be preformed prior to the original query, allowing for fine-grained authorization control.

Setting Up Prisma Schema

In the schema.prisma file, located in the prisma folder, replace the code with the following snippet, allowing for MySQL usage.

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

Create an .env file, which will contain the DATABASE_URL from PlanetScale. Learn how to connect a PlanetScale database.

Make sure to append &&sslaccept=strict at the end of your database URL in the .env file.

Next, create a new model Star , containing basic attributes for testing.

model Star {
  id            Int      @id @default(autoincrement())
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  name          String   @db.VarChar(255)
  constellation String   @db.VarChar(255)
}

Complete it off by pushing the changes to the database with the following command:

npx prisma db push

Extending a Prisma Query

Back in the Node.js project, let’s create an extension of a findMany query which returns a list of records for a given model.

const xprisma = prisma.$extends({
    query: {
    	star: {
    		async findMany({ model, operation, args, query }) {
    			if (await oso.authorize(USER, operation, model)) return query(args);
        return new Error('Not authorized');
    		},
    	},
    },
});

Replace USER with actual data.

The method will execute the findMany query on the star model (typings were automatically generated by Prisma in the previous steps), while performing an authorization check with Oso Cloud.

Method will return an object, containing arguments like model we’re querying and the operation we’re performing.

In this case, the arguments in the oso.authorize() method will be populated with the following data (USER, "findMany", "Star").

If the action is authorized, the query will be executed and the results returned.

Test the Integration

To test the integration of Oso and PlanetScale, we will first populate the table with some data.

await prisma.star.create({
    data: {
        name: 'Sirius',
        constellation: 'Canis Major',
    },
});

Then, we can call the findMany query from xprisma with the following method:

const stars = await xprisma.star.findMany();
console.log(stars);

If authorized, this should output the following:

Positive query output

And if not, an error will be thrown:

Query error

Next Steps and Resources

In this guide you learned how to integrate Oso and PlanetScale by using Prisma, a powerful ORM tool.

With Oso's adaptable policy language and PlanetScale's strong distributed database system, you can easily enforce strict access control and authorization rules while smoothly handling your data.

A good place to learn Oso from head to toe is the extensive documentation, blog containing tutorials, thought pieces and updates, or perhaps Oso Academy - a series of technical guides for understanding and applying authorization.

#access #oso #planetscale #prisma