npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@knotel/authorize

v0.11.0

Published

Opinionated authorization middleware for federated GraphQL services.

Downloads

6

Readme

Getting Started

Import this package into any Apollo federated service within the mono infrastructure where the User <> Profile association exists.

@knotel/authorize makes a few assumptions:

  • The federated service has Profile and Role tables.
  • The Role table has a policy column containing a valid CSV format string.
  • The policy CSV string is formatted in the following syntax:
p, resource, action

The node-casbin depedency is normally case-sensitive and requires comma-separated values to be padded with whitespace. However, the isAuthorized method is written to be more flexible. So case and space should not matter.

Installation

yarn add @knotel/authorize

Usage

app/graphql/permissions/index.js

For more information on how to construct your permissions directory, see the official graphql-shield documentation.

const { permissions, isAuthorized } = require('@knotel/authorize')

// permissions is a graphql-shield function (see shield)
module.exports = permissions({
    Query: {
        Resource: isAuthorized('Read'),
        allResources: isAuthorized('List'),
    },
    Mutation: {
        createResource: isAuthorized('Create'),
        updateResource: isAuthorized('Edit'),
        deleteResource: isAuthorized('Delete'),
    },
})

app/graphql/schema.js

Nothing functional is going on here. Just imported and exported through this file for convenience and cleaner imports.

const typeDefFiles = require('./typedefs')
const resolvers = require('./resolvers')
const permissions = require('./permissions')

const typeDefString = typeDefFiles.join('\n')
const typeDefs = gql(typeDefString)

module.exports = {
  typeDefs,
  resolvers,
  permissions,
}

app/app.js Import the permissions dependency and apply it as middleware to your GraphQL schema. Then, in your global context object, add the logic to derive the policy document for a given user and inject it into the context object.

isAuthorized checks for the scopes specified in your permissions directory in the policy document provided by the context object.

const { applyMiddleware } = require('graphql-middleware')

const { typeDefs, resolvers, permissions } = require('./graphql/schema')

const schema = buildFederatedSchema([{
    typeDefs,
    resolvers
}])

const server = new ApolloServer({
    schema: applyMiddleware(schema, permissions),
    context: ({ req, context }) => {
        const profile = await Profile.findOne({
            where: {
                userId: req.user.id
            }
        })
        const role = await profile.getRole({ raw: true })
        return { user: req.user, policy: role.policy, req: req }
    },
})

Creation of Policy Documents

Ideally, the policy document should be stored on the role record belonging to the user. Not the user or profile itself.

That being said, the injection of the policy property into the global context object offers you the flexibility to derive the policy document from wherever you choose.

app/db/seeders/*.js

To create your policy documents, this package also includes a small utility to use in the creation of your seed Role records.

const { generatePolicy } = require('@knotel/authorize')

const resourcePermissions = [{
    name: 'Space',
    resource: ['List', 'Read', 'Create', 'Edit', 'Delete']
}]

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.query(`
      UPDATE "Role" SET "policy" = '${generatePolicy(resourcePermissions)}' WHERE "Role"."name" = 'ACCOUNT_EXECUTIVE';
    `)
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface.sequelize.query(`
      UPDATE "Role" SET "policy" = NULL WHERE "Role"."name" = 'ACCOUNT_EXECUTIVE';
    `)
  },
}

Validation of Policy Documents

An important aspect of ensuring a bug-free authorization framework is persisting policy documents in the format expected by the @knotel/authorize.

const { isValidPolicy } = require('@knotel/authorize')

const role = Role.findByPk(req.params.id)
const isValid = await isValidPolicy(role.policy)

Resource Authorization

| Prefix | Query/Mutation Example | Example Args | Scope | Description | |--------|------------------------|--------------|-----------|---------------------------------------------------------| | all | allResources | () | list | "I want all resources without knowing IDs." | | own | allResources | () | ownlist | "I want all my/our resources without knowing IDs." | | any | Resource | (id) | read | "I want any resource assuming I know the ID." | | own | Resource | (id) | ownread | "I want my resource assuming I know the ID." | | any | deleteResource | (id) | delete | "I want my resource assuming I know the ID." | | own | deleteResource | (id) | owndelete | "I want to delete my resource assuming I know the ID." | | any | updateResource | (id, params) | edit | "I want to update any resource assuming I know the ID." | | own | updateResource | (id, params) | ownedit | "I want to update my resource assuming I know the ID." | | new | createResource | (params) | create | "I want to create a new resource." |