Skip to content

Commit

Permalink
feat(otplease): Remove figgy-pudding
Browse files Browse the repository at this point in the history
  • Loading branch information
evocateur committed Nov 20, 2020
1 parent 68f5807 commit 45ee52e
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 16 deletions.
31 changes: 21 additions & 10 deletions core/otplease/otplease.js
@@ -1,11 +1,11 @@
"use strict";

const figgyPudding = require("figgy-pudding");
const prompt = require("@lerna/prompt");

const OtpPleaseConfig = figgyPudding({
otp: {},
});
/**
* @typedef {object} OneTimePasswordCache - Passed between concurrent executions
* @property {string} [otp] The one-time password, passed as an option or received via prompt
*/

// basic single-entry semaphore
const semaphore = {
Expand Down Expand Up @@ -38,13 +38,20 @@ const semaphore = {
module.exports = otplease;
module.exports.getOneTimePassword = getOneTimePassword;

/**
* Attempt to execute Promise callback, prompting for OTP if necessary.
* @template {Record<string, unknown>} T
* @param {(opts: T) => Promise<unknown>} fn
* @param {T} _opts The options to be passed to `fn`
* @param {OneTimePasswordCache} otpCache
*/
function otplease(fn, _opts, otpCache) {
// NOTE: do not use 'otpCache' as a figgy-pudding provider directly as the
// otp value could change between async wait points.
const opts = OtpPleaseConfig(Object.assign({}, otpCache), _opts);
// always prefer explicit config (if present) to cache
const opts = { ...otpCache, ..._opts };
return attempt(fn, opts, otpCache);
}

/** @returns {Promise<unknown>} */
function attempt(fn, opts, otpCache) {
return new Promise((resolve) => {
resolve(fn(opts));
Expand All @@ -56,15 +63,15 @@ function attempt(fn, opts, otpCache) {
} else {
// check the cache in case a concurrent caller has already updated the otp.
if (otpCache != null && otpCache.otp != null && otpCache.otp !== opts.otp) {
return attempt(fn, opts.concat(otpCache), otpCache);
return attempt(fn, { ...opts, ...otpCache }, otpCache);
}
// only allow one getOneTimePassword attempt at a time to reuse the value
// from the preceeding prompt
return semaphore.wait().then(() => {
// check the cache again in case a previous waiter already updated it.
if (otpCache != null && otpCache.otp != null && otpCache.otp !== opts.otp) {
semaphore.release();
return attempt(fn, opts.concat({ otp: otpCache.otp }), otpCache);
return attempt(fn, { ...opts, ...otpCache }, otpCache);
}
return getOneTimePassword()
.then(
Expand All @@ -85,13 +92,17 @@ function attempt(fn, opts, otpCache) {
}
)
.then((otp) => {
return fn(opts.concat({ otp }));
return fn({ ...opts, otp });
});
});
}
});
}

/**
* Prompt user for one-time password.
* @returns {Promise<string>}
*/
function getOneTimePassword(message = "This operation requires a one-time password:") {
// Logic taken from npm internals: https://git.io/fNoMe
return prompt.input(message, {
Expand Down
3 changes: 1 addition & 2 deletions core/otplease/package.json
Expand Up @@ -31,7 +31,6 @@
"test": "echo \"Run tests from root\" && exit 1"
},
"dependencies": {
"@lerna/prompt": "file:../prompt",
"figgy-pudding": "^3.5.1"
"@lerna/prompt": "file:../prompt"
}
}
6 changes: 2 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 45ee52e

Please sign in to comment.