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

openkey

v0.0.10

Published

Fast authentication layer for your SaaS, backed by Redis.

Downloads

22

Readme

Tired of not owning the authentication flow of your SaaS?

openkey is a ~200 lines backed in Redis for control authentication flow. You can deploy to any cloud provider, no vendor lock-in, being cheap at any scale & focused in performance to authenticate requests.

Installation

First, Install openkey from your preferred node package manager:

pnpm install openkey

CLI

openkey is also available as CLI when you install it globally in your system:

npm install -g openkey

After that, you can access to any command from your terminal:

❯ openkey
openkey> help
version exit keys plans usage stats
openkey> version
1.0.0
openkey> exit

Usage

After installation, initialize openkey:

const Redis = require('ioredis')
const redis = new Redis()
const openkey = require('openkey')({ redis })

you can prepend all the keys by passing prefix:

const openkey = require('openkey')({ redis, prefix: 'http:' })

These will allow you to acess to openkey core concepts: plans, keys, usage, and stats.

.plans

It represents the quota, rate limit, and throttle information specified as a plan.

.create

It creates a new plan:

const plan = await openkey.plans.create({
  name: 'free tier',
  metadata: { tier: 'free' },
  limit: 3000,
  period: '1d'
})

The options accepted are:

  • idstring: The id of the plan, it cannot contain whitespaces.
  • periodstring: The time window which the limit applies. It accepts ms syntax.
  • limitnumber: The target maximum number of requests that can be made in a given time period.
  • metadataobject: A flat object containing additional information. Pass null or '' to remove all the metadata fields.

Any other field provided will be omitted.

Returns: an object with the options specified, plus:

  • createdAtnumber: The timestamp when the object was created.
  • updatedAtnumber: The last timestamp when the object was modified.

.list

It retrieves all the plans:

const plans = await openkey.plans.list()

.retrieve

It retrieves a plan by id:

const { createdAt, updatedAt, ...plan } = await openkey.plans.retrieve('free_tier')

Returns: the plan object, or null if it is not found.

.update

It updates a plan by id:

const { updatedAt, ...plan } = await openkey.plans.update('free_tier', {
  limit: 1000
})

You can't update the id. Also, in the same way than .create, any other field that is not a supported option will be omitted.

Returns: the updated plan object. If the plan is not found, this method will throw an error.

.del

It deletes a plan by id:

await openkey.plans.del('free_tier')

It will throw an error if:

  • The plan has key associated. In that case, first keys needs to be deleted.
  • The plan id doesn't exist.

Returns A boolean confirming the plan has been deleted.

.keys

It represents the credentials used for authenticating a plan.

.create

It creates a new key:

/*
 * A random 16 length base58 key is created by default.
 */
const key = await openkey.key.create()
console.log(key.value) // => 'oKLJkVqqG2zExUYD'

/**
 * You can provide a value to use.
 */
const key = await openkey.key.create({ value: 'oKLJkVqqG2zExUYD' })

/**
 * The key can be associated with a plan when it's created.
 */
const key = await openkey.key.create({ value: 'oKLJkVqqG2zExUYD', plan: plan.id })

The options accepted are:

  • valuestring: The value of the key, being a base58 16 length key generated by default.
  • enabledstring: It determines if the key is active, being true by default.
  • metadataobject: A flat object containing additional information. Pass null or '' to remove all the metadata fields.

Any other field provided will be omitted.

Returns: an object with the options specified, plus:

  • createdAtnumber: The timestamp when the object was created.
  • updatedAtnumber: The last timestamp when the object was modified.

.retrieve

It retrieves a key by id:

const { createdAt, updatedAt, ...key } = await openkey.key.retrieve('AN4fJ')

Returns: the key object, or null if it is not found.

.update

It updates a key by id:

const { updatedAt, ...key } = await openkey.key.update(value, {
  enabled: false
})

In the same way than .create, any other field that is not a supported option will be omitted.

Returns: the updated key object. If the key is not found, this method will throw an error.

.del

It deletes a key by value:

await openkey.plans.del(value)

It will throw an error if the key value doesn't exist.

Returns A boolean confirming the plan has been deleted.

.usage

It returns the current usage of a key that is associated with a plan:

const usage = await openkey.usage(key.value)
console.log(usage)
// {
//   limit: 3,
//   remaining: 3,
//   reset: 1714571966087,
//   pending: Promise { [] }
// }

.increment

Similar to the previous method, but increments the usage by one before returning:

const usage = await openkey.usage.increment(key.value)
// {
//   limit: 3,
//   remaining: 2,
//   reset: 1714571966087,
//   pending: Promise { [] }
// }

Additionally you can increment specifying the quantity:

const usage = await openkey.usage.increment(key.value, { quantity: 3 })
// {
//   limit: 3,
//   remaining: 0,
//   reset: 1714571966087,
//   pending: Promise { [] }
// }

.stats

It returns the count per every day for a given API key:

const stats = await openkey.stats(key.value)
console.log(stats)
// [
//   { date: '2024-05-01', count: 1 },
//   { date: '2024-05-02', count: 10 },
//   { date: '2024-05-03', count: 5 }
// ]

Compression & serialization

By default, openkey uses JSON serialization without compression for two reasons:

  • The payload isn't large enough to take advantage of compression.
  • Storing compressed data makes the content unreadable without first decompressing it.

You can customize serialize and deserialize when openkey is instantiated to define how you want your data to be handled.

For example, you can combine openkey with compress-brotli to store compressed data painlessly:

const compressBrotli = require('compress-brotli')
const redis = new Redis()

const openkey = require('openkey')({
  redis,
  serialize: async data => brotli.serialize(await brotli.compress(data)),
  deserialize: data => brotli.decompress(brotli.deserialize(data))
})

HTTP fields

openkey has been designed to play well according to RateLimit header fields for HTTP:

module.exports = (req, res) => {
  const apiKey = req.headers['x-api-key']
  if (!apiKey) send(res, 401)
  const { pending, ...usage } = await openkey.usage.increment(apiKey)
  const statusCode = usage.remaining > 0 ? 200 : 429
  res.setHeader('X-Rate-Limit-Limit', usage.limit)
  res.setHeader('X-Rate-Limit-Remaining', usage.remaining)
  res.setHeader('X-Rate-Limit-Reset', usage.reset)
  return send(res, statusCode, usage)
}

Stripe integration

openkey is about making pricing a part of your product development.

It's an excellent idea to combine it with Stripe:

// https://docs.stripe.com/billing/subscriptions/usage-based/implementation-guide
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('sk_test_VZqeYMqkpa1bMxXyikghdPCu');

const count = await openkey.usage.get('{{CUSTOMER_KEY}}')

const meterEvent = await stripe.billing.meterEvents.create({
  event_name: 'alpaca_ai_tokens',
  payload: {
    value: count,
    stripe_customer_id: '{{CUSTOMER_ID}}',
  },
});

Read more about Usage-based billing at Stripe.

Error handling

Every possible error thrown by openkey has the name OpenKeyError unique code associated with it.

if (error.name === 'OpenKeyError') {
  return send(res, 400, { code: error.code, message: error.message })
} else {
  return send(res, 500)
}

This makes it easier to apply any kind of handling in your application logic.

You can find the list errors in the source code.

Design decisions

Why Redis?

Mainly because it's a cheap in-memory database at scale, and mature enough to prevent vendor lock-in.

We considered other alternatives such as SQLite, but according to these requeriments Redis is a no brain choice.

Why not TypeScript?

This library is intended to be used millions of times every day. We wanted to have granular control as much as possible, and adding a TypeScript transpilation layer isn't ideal from a performance and maintenance perspective.

Why key/value?

Originally this library was implemented using hashes, but then since values are stored as string, it's necessary to cast value (for example, from string to number).

Since we need to do that all the time, we prefer to use key/value. Also this approach allow to customize serializer/deserializer, which is JSON by default.

License

openkey © microlink.io, released under the MIT License. Authored and maintained by microlink.io with help from contributors.

microlink.io · GitHub microlink.io · Twitter @microlinkhq