index.js 5.85 KB
"use strict";

var os = require("os");
var path = require("path");

// How Storage Works in Greenlock: High-Level Call Stack
//
// nested === skipped if parent succeeds (or has cached result)
//
// tls.SNICallback()                                      // TLS connection with SNI kicks of the request
//
//   greenlock.approveDomains(opts)                       // Greenlokc does some housekeeping, checks for a cert in
//                                                        // an internal cache, and only asks you to approve new
//                                                        // certificate // registration if it doesn't find anything.
//                                                        // In `opts` you'll receive `domain` and a few other things.
//                                                        // You should return { subject: '...', altnames: ['...'] }
//                                                        // Anything returned by approveDomains() will be received
//                                                        // by all plugins at all stages
//
//     greenlock.store.certificates.check()               // Certificate checking happens after approval for several
//                                                        // reasons, including preventing duplicate registrations
//                                                        // but most importantly because you can dynamically swap the
//                                                        // storage plugin right from approveDomains().
//     greenlock.store.certificates.checkKeypair()        // Check for a keypair associated with the domain
//
//     greenlock.store.accounts.check()                   // Optional. If you need it, look at other Greenlock docs
//
//     greenlock.store.accounts.checkKeypair()            // Check storage for registered account key
//       (opts.generateKeypair||RSA.generateKeypair)()    // Generates a new keypair
//       greenlock.core.accounts.register()               // Registers the keypair as an ACME account
//       greenlock.store.accounts.setKeypair()            // Saves the keypair of the registered account
//       greenlock.store.accounts.set()                   // Optional. Saves superfluous ACME account metadata
//
//     greenlock.core.certificates.register()             // Begin certificate registration process & housekeeping
//       (opts.generateKeypair||RSA.generateKeypair)()    // Generates a new certificate keypair
//       greenlock.acme.certificates.register()           // Performs the ACME challenge processes
//       greenlock.store.certificates.setKeypair()        // Saves the keypair for the valid certificate
//       greenlock.store.certificates.set()               // Saves the valid certificate

////////////////////////////////////////////
// Recap of the high-level overview above //
////////////////////////////////////////////
//
//  None of this ever gets called except if there's not a cert already cached.
//  That only happens on service boot, and about every 75 days for each cert's renewal.
//
//  Therefore, none of this needs to be fast, fancy, or clever
//
//  For any type of customization, whatever is set in `approveDomains()` is available everywhere else.

// Either your user calls create with specific options, or greenlock calls it for you with a big options blob
module.exports.create = function(config) {
    // Bear in mind that the only time any of this gets called is on first access after startup, new registration, and
    // renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however - if you have
    // more than 10,000 domains, for example.

    // basic setup
    var store = {
        accounts: require("./accounts.js"),
        certificates: require("./certificates.js")
    };

    // For you store.options should probably start empty and get a minimal set of options copied from `config` above.
    // Example:
    //store.options = {};
    //store.options.databaseUrl = config.databaseUrl;

    // In the case of greenlock-store-fs there's a bunch of legacy stuff that goes on, so we just clobber it all on.
    // Don't be like greenlock-store-fs (see note above).
    store.options = mergeOptions(config);
    store.accounts.options = store.options;
    store.certificates.options = store.options;

    if (!config.basePath && !config.configDir) {
        console.info("Greenlock Store FS Path:", store.options.configDir);
    }

    return store;
};

///////////////////////////////////////////////////////////////////////////////
//                                  Ignore                                   //
///////////////////////////////////////////////////////////////////////////////
//
// Everything below this line is just implementation specific
var defaults = {
    basePath: path.join(os.homedir(), ".config", "greenlock"),

    accountsDir: path.join(":basePath", "accounts", ":directoryUrl"),
    serverDirGet: function(copy) {
        return (copy.directoryUrl || copy.server || "")
            .replace("https://", "")
            .replace(/(\/)$/, "")
            .replace(/\//g, path.sep);
    },
    privkeyPath: path.join(":basePath", ":env", ":subject", "privkey.pem"),
    fullchainPath: path.join(":basePath", ":env", ":subject", "fullchain.pem"),
    certPath: path.join(":basePath", ":env", ":subject", "cert.pem"),
    chainPath: path.join(":basePath", ":env", ":subject", "chain.pem"),
    bundlePath: path.join(":basePath", ":env", ":subject", "bundle.pem")
};
defaults.configDir = defaults.basePath;

function mergeOptions(configs) {
    if (!configs.serverKeyPath) {
        configs.serverKeyPath =
            configs.domainKeyPath ||
            configs.privkeyPath ||
            defaults.privkeyPath;
    }

    Object.keys(defaults).forEach(function(key) {
        if (!configs[key]) {
            configs[key] = defaults[key];
        }
    });

    return configs;
}