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 🙏

© 2025 – Pkg Stats / Ryan Hefner

greenlock-store-memory

v3.0.3

Published

An in-memory reference implementation for account, certificate, and keypair storage strategies in Greenlock

Readme

greenlock-store-memory

An in-memory reference implementation of a Certificate and Keypair storage strategy for Greenlock v2.7+ (and v3)

Usage

var greenlock = require('greenlock');

// This in-memory plugin has only one option: 'cache'.
// We could have It's used so that we can peek and poke at the store.
var cache = {};
var gl = greenlock.create({
  store: require('greenlock-store-memory').create({ cache: cache })
, approveDomains: approveDomains
  ...
});

How to build your own module:

TL;DR: Just take a look the code here, and don't over think it.

Also, you have the flexibility to get really fancy. Don't! You probably don't need to (unless you already know that you do).

DON'T BE CLEVER. Do it the dumb way first.

In most cases you're just implementing dumb storage. If all you do is JSON.stringify() on set (save) and JSON.parse() after check (get) and just treat it as a blob with an ID, you'll do just fine. You can always optimize later.

Promises vs Thunks ("node callbacks") vs Synchronous returns: You can use whatever style you like best. Everything is promisified under the hood.

Whenever you have neither a result, nor an error, you must always return null (instead of 'undefined').

storage strategy vs approveDomains()

The most important thing to keep in mind: approveDomains() is where all of the implementation-specific logic goes.

If you're writing a storage strategy (presumably why you're here), it's because you have logic in approveDomains() that isn't supported by existing strategies. That makes it tempting to start thinking about things backwards, letting your implementation-specific logic creep into your storage strategy. DON'T DO IT.

Keep in mind that, ultimately, it takes human decision / interaction / configuration to add, remove, or modify the collection of domains that are allowed, and how many / which domains are listed on each certificate - all of which is a completely separate process that lives outside of Greenlock (i.e uploading a site to a new folder).

The coupling between the method chosen for storage and the method chosen for approval is inherint, but keep it loose.

Lastly, it would be appropriate to include an example approveDomains() with your implementation for reference.

0. approveDomains() is the kick off

approveDomains() is called only when there is no certificate for a given domain in Greenlock's internal cache and when that certificate is "renewable" (typically 15 days before expiration, which is configurable).

The user (perhaps you) will have checked in their database (or config file or file system) and retrieved relevant details (email associated with the domain, related domains that belong as altnames on the certificate, etc).

Those options will be available to all storage and challenge strategies. In fact, they can even change which strategy is used (i.e. some users using a Digital Ocean strategy for DNS challenges, others using Route53).

function approveDomains(opts) {
  var info = userDb.getInfo(opts.domain);
  if (!info) { throw new Error("ignoring junk request, bad domain"); }

  opts.email = info.certificateOwner;
  opts.subject = info.certificateSubject
  opts.domains = info.certificateAltnames;

  return opts; // or Promise.resolve(opts);
}

1. Implement accounts.setKeypair

First, you should implement accounts.setKeypair(). Just treat it like dumb storage.

This only gets called after a new account has already been created successfully. That will only happen when a completely new certificate is going to be issued (not renewal), and there's no user account already associate with that set of domains.

store.accounts.setKeypair = function (opts) {
  console.log('accounts.setKeypair:', opts);

  var id = opts.account.id || opts.email || 'default';
  var keypair = opts.keypair;

  cache.accountKeypairs[id] = JSON.stringify({
    privateKeyPem: keypair.privateKeyPem
  , privateKeyJwk: keypair.privateKeyJwk
  });

  return null; // or Promise.resolve(null);
};

2. Implement accounts.checkKeypair

Whatever you did above, you just do the opposite instead. Tada!

store.accounts.checkKeypair = function (opts) {
  console.log('accounts.checkKeypair:', opts);

  var id = opts.account.id || opts.email || 'default';
  var keyblob = cache.accountKeypairs[id];

  if (!keyblob) { return null; }

  return JSON.parse(keyblob);
};

3. (and 4.) Optionally save ACME account metadata

You should probably skip this and not worry about it.

However, if you have a special need for it, or if you want to shave off an ACME API call, you can save the account kid (a misnomer intended to mean "key id", but actually refers to an arbitrary ACME URL, used to identify the account).

store.accounts.set = function (opts) {
  console.log('accounts.set:', opts);
  return null;
};
store.accounts.check = function (opts) {
  var id = opts.account.id || opts.email || 'default';
  console.log('accounts.check:', opts);
  return null;
};

If you don't implement these the account key will be used to "recover" the kid as necessary. You don't have to worry though, it doesn't create a duplicate accounts or have any other negative side affects other than an additional API call as needed.

5. Implement a method to save certificate keypairs

Each certificate is supposed to have a unique keypair, which must not be the same as the account keypair.

Again, just treat it like a blob in dumb storage and you'll be fine.

This is the same as accounts.setKeypair(), but using a different idea. You could even use the same data store in most cases because the IDs aren't likely to clash.

store.certificates.setKeypair = function (opts) {
  console.log('certificates.setKeypair:', opts);

  var id = opts.certificate.kid || opts.certificate.id || opts.subject;
  var keypair = opts.keypair;

  cache.certificateKeypairs[id] = JSON.stringify({
    privateKeyPem: keypair.privateKeyPem
  , privateKeyJwk: keypair.privateKeyJwk
  });

  return null;
};

6. Implement a method to get certificate keypairs

You know the drill. Same as accounts.checkKeypair(), but a different ID.

This isn't called until after the certificate retrieval is successful.

Note: Every account must have a unique account key and account keys are not allowed to be used as certificate keys. However, you could use the same certificate key for all domains on a device (i.e. a server) or an account.

store.certificates.checkKeypair = function (opts) {
  console.log('certificates.checkKeypair:', opts);

  var id = opts.certificate.kid || opts.certificate.id || opts.subject;
  var keyblob = cache.certificateKeypairs[id];

  if (!keyblob) { return null; }

  return JSON.parse(keyblob);
};

7. Implement a method to save certificates

Whenever the ACME process completes successfully, you get a shiny new certificate with all of the domains you requested.

It's a good idea to save them - otherwise you run the risk of running up your rate limit and getting blocked as your server restarts, respawns, auto-scales, etc.

store.certificates.set = function (opts) {
  console.log('certificates.set:', opts);

  var id = opts.certificate.id || opts.subject;
  var pems = opts.pems;
  cache.certificates[id] = JSON.stringify({
    cert: pems.cert
  , chain: pems.chain
  , subject: pems.subject
  , altnames: pems.altnames
  , issuedAt: pems.issuedAt   // a.k.a. NotBefore
  , expiresAt: pems.expiresAt // a.k.a. NotAfter
  });

  return null;
};

Note that chain is likely to be the same for all certificates issued by a service, but there's no guarantee. The service may rotate which keys do the signing, for example.

8. Implement a method to get certificates

Lastly, you just need a way to fetch the result of all the work you've done.

store.certificates.check = function (opts) {
  console.log('certificates.check:', opts);

  var id = opts.certificate.id || opts.subject;
  var certblob = cache.certificates[id];

  if (!certblob) { return null; }

  return JSON.parse(certblob);
};

Huzzah!

There you go - you basically just have 8 setter and getter functions that usually act as dumb storage, but that you can tweak with custom options if you need to.

Remember: Keep It Stupid-Simple

:D