diff --git a/README.md b/README.md index ba463eb..10c7a55 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ there's an internet connection. The Mushpup web application provides access to an interface to quickly retrieve passwords. It is available over the internet at the following sites: -- http://mushpup.org/ -- https://mushpup-demo.appspot.com/ +- https://mushpup.org/ +- https://klenwell.com/mushpup/ ## Installation diff --git a/docs/css/bootstrap.css b/docs/css/bootstrap.css new file mode 100644 index 0000000..f8362de --- /dev/null +++ b/docs/css/bootstrap.css @@ -0,0 +1,24 @@ +/* Sticky footer styles +-------------------------------------------------- */ +html { + position: relative; + min-height: 100%; +} +body { + /* Margin bottom by footer height */ + margin-bottom: 66px; +} +.footer { + position: absolute; + bottom: 0; + width: 100%; + /* Set the fixed height of the footer here */ + height: 66px; + background-color: #f5f5f5; + padding-top: 12px; +} +.footer p { + margin-bottom: 4px; + text-align: center; + color: #999999; +} diff --git a/docs/css/mushpup-ui.css b/docs/css/mushpup-ui.css new file mode 100644 index 0000000..752aa4c --- /dev/null +++ b/docs/css/mushpup-ui.css @@ -0,0 +1,160 @@ +/* + * State Rules + */ +.rolled-up { + display: none; +} + +div.mushpup { + max-width: 384px; + padding: 40px 0px; + margin: 0 auto; +} + + +/* + * Input Form + */ +div.mushpup form {} +div.mushpup form .form-control { + position: relative; + height: auto; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 10px; + font-size: 16px; +} +div.mushpup form .form-control:focus { + z-index: 2; +} +div.mushpup form input[type="locus"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +div.mushpup form input[type="pocus"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +div.action-panel { + width: 100%; + margin: 0 auto; + padding: 0; +} +div.action-panel span.action { + display: inline-block; + margin: 0 auto; + padding: 0; + text-align: center; + width: 16%; +} +div.action-panel span.action.mush { + width: 64%; +} +div.action-panel button.mush { + display: inline-block; + margin: 0 auto; +} +div.action-panel span.circle { + display: inline-block; + width: 50px; + height: 50px; + border-radius: 25px; + -moz-border-radius: 25px; + -webkit-border-radius: 25px; + line-height: 50px; +} +div.action-panel span.circle.button { + text-align: center; + background-color: #337ab7; + box-shadow: 0 0 2px #2e6da4; + cursor: pointer; + color: white; +} +div.action-panel span.circle.button:hover { + background-color: #286090; + box-shadow: 0 0 2px #204d74; +} + +/* + * Reveal Panel + */ +div.output-panel { + display: none; + margin-top: 8px; + width: 100%; +} + +/* Payload / Rulers */ +div.output-panel div.payload { + margin: 0 auto; + width: 100%; + background-color: #eee; + text-align: center; +} +div.output-panel div.payload span.c { + display: inline-block; + width: 16px; + text-align: center; + padding: 0px; + margin: 0px; + font-family: Inconsolata,"Lucida Console",Monaco,monospace; +} + +div.output-panel div.payload div.ruler { + background-color: white; + font-size: 14px; +} +div.output-panel div.payload div.ruler.upper { + color: red; + border-top: solid red 1px; + border-bottom: solid red 1px; +} +div.output-panel div.payload div.ruler.lower { + color: blue; + border-top: solid blue 1px; + border-bottom: solid blue 1px; +} +div.output-panel div.payload div.ruler span.c {} + +div.output-panel div.payload div.hash { + margin: 0 auto; + padding: 0; + width: 100%; + background-color: white; + text-align: center; +} +div.output-panel div.payload div.hash span.group { + padding: 0; + display: inline-block; +} +div.payload div.hash span.group.west { + background-color: #ffdddd; + color: white; +} +div.payload div.hash span.group.east { + background-color: #ddddff; + color: white; +} +div.payload div.hash span.group.central { + background-color: white; + color: #dddddd; +} +div.output-panel div.payload div.hash span.c { + font-size: 20px; + padding: 4px 0; +} + +/* Alerts */ +div.output-panel div.alerts { + margin-bottom: 4px; +} +div.output-panel div.alerts div.alert { + margin-bottom: 1px; + padding-top: 4px; + padding-bottom: 4px; + padding-left: 8px; +} diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 0000000..92e87c3 Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..c0401d6 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,131 @@ + + + + + + + + + + + + + Mushpup + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
+

To use Mushpup, first:

+
    +
  1. Pick a Mushpup secret word (a master password only you remember)
  2. +
  3. Pick a Mushpup number (between 8 and 16)
  4. +
  5. Pick a Mushpup side (left, right, or middle)
  6. +
+

You will want to use these by habit, so make picks that you will remember.

+
+
+

Then, to get a new password:

+
    +
  1. Identify new site (e.g. github.com)
  2. +
  3. Set your user name (e.g. klenwell)
  4. +
  5. Set your site input based on site/user (e.g. github.com/klenwell)
  6. +
+

Use the Mushpup form above when you register to a new site. Now is the time to use the confirm button to make sure you enter your secret word correctly.

+
+
+

Finally, to retrieve the password:

+
    +
  1. Input site input and Mushpup secret word in the Mushpup form
  2. +
  3. Using your Mushpup number and Mushpup side, copy your password
  4. +
  5. Paste the password into the site login password field.
  6. +
+

Now you can get you password any time you need it. In this way, by remembering a single password, you get a unique password for every site you use

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/js/mushpup-form.js b/docs/js/mushpup-form.js new file mode 100644 index 0000000..70416a4 --- /dev/null +++ b/docs/js/mushpup-form.js @@ -0,0 +1,252 @@ + +var MushpupForm = (function() { + // Constants + var MUSH_TIMEOUT = 15; // seconds + var RESET_TIMEOUT = 60; + + // Globals + var unmushTimer; + var resetTimer; + var $mushButton = $('button.mush'); + + var prepareRuler = function() { + var upperRuler = '>***5****0****5****0***>'; + var lowerRuler = '<***0****5****0****5***<'; + var groups = ['west', 'central', 'east']; + var $upperRuler = $('div.upper.ruler'); + var $lowerRuler = $('div.lower.ruler'); + + // Build groups of ruler characters + jQuery.each(groups, function(n, group) { + var $upperGroup = $('').addClass('upper group ' + group); + var $lowerGroup = $('').addClass('lower group ' + group); + + for (var i=0; i < 8; i++) { + var index = n * 8 + i; + var upperChar = upperRuler[index].replace('*', '•'); + var lowerChar = lowerRuler[index].replace('*', '•'); + var $upperSpan = $('').addClass('c').html(upperChar); + var $lowerSpan = $('').addClass('c').html(lowerChar); + $upperGroup.append($upperSpan); + $lowerGroup.append($lowerSpan); + } + + $upperRuler.append($upperGroup); + $lowerRuler.append($lowerGroup); + }); + }; + + var prepareMushButtonHandler = function() { + $mushButton.data('form-open', true); + $mushButton.on('click', function() { + var ok = false; + + if ( formIsOpen() ) { + ok = onMush(); + } + else { + ok = onUnmush(); + } + + if ( ok ) { + toggleForm(); + } + + return false; + }); + }; + + var prepareResetButtonHandler = function() { + $('span.button.reset').on('click', function() { + resetForm(); + }); + }; + + var prepareConfirmButtonHandler = function() { + $('span.button.confirm').on('click', function() { + toggleConfirmField(); + }); + }; + + var onMush = function() { + // Validate confirm field if present + var confirmFieldIsPresent = $("div.confirmation").is(":visible"); + + if ( confirmFieldIsPresent ) { + var pocus = $('input#pocus').val().trim(); + var confirmation = $('input#pocus-confirm').val().trim(); + + if ( pocus !== confirmation ) { + alert("Your mushpup secret words didn't match. Please carefully re-enter them."); + return false; + } + } + + // Generate hash + var hash = generateHash(); + validateInput(); + updateHash(hash); + $mushButton.text('unmush'); + + // Unmush after given period of time + clearTimeout(unmushTimer); + unmushTimer = setTimeout(function() { + $mushButton.click(); + }, MUSH_TIMEOUT * 1000); + + // Reset form completely after given period of time + restartResetTimer(); + + return true; + }; + + var onUnmush = function() { + clearTimeout(unmushTimer); + clearPayload(); + restartResetTimer(); + return true; + }; + + var toggleForm = function() { + $('fieldset.locus-pocus').slideToggle('slow'); + $('panel.reveal').slideToggle('slow', swapFormState); + }; + + var showForm = function() { + $('fieldset.locus-pocus').slideDown('slow', function() { + $mushButton.data('form-open', true); + }); + $('panel.reveal').slideUp('slow'); + + // Returns promise that can be used for callback when form present + var formIsVisible = $('fieldset.locus-pocus').promise(); + return formIsVisible; + }; + + var clearInputFields = function() { + $('input#locus').val(''); + $('input#pocus').val(''); + $('input#pocus-confirm').val(''); + }; + + var clearPayload = function() { + $mushButton.text('mush'); + var hash = '------------------------'.replace(/-/g, '•'); + updateHash(hash); + }; + + var resetForm = function() { + clearTimeout(unmushTimer); + clearTimeout(resetTimer); + clearPayload(); + clearInputFields(); + var formIsVisible = showForm(); + + $.when(formIsVisible).then(function() { + rollupConfirmField(); + $('input#locus').focus(); + }); + }; + + var toggleConfirmField = function() { + $('div.confirmation').slideToggle('slow', function() { + if ( $("div.confirmation").is(":visible") ) { + $('input#pocus-confirm').focus(); + } + }); + }; + + var rollupConfirmField = function() { + if ( $("div.confirmation").is(":visible") ) { + toggleConfirmField(); + } + }; + + var restartResetTimer = function() { + clearTimeout(resetTimer); + resetTimer = setTimeout(function() { + resetForm(); + }, RESET_TIMEOUT * 1000); + }; + + var swapFormState = function() { + $mushButton.data('form-open', !(formIsOpen())); + }; + + var formIsOpen = function() { + return !!($mushButton.data('form-open')); + }; + + var generateHash = function() { + var locus = $('input#locus').val().trim(); + var pocus = $('input#pocus').val().trim(); + return Mushpup.mush(locus, pocus); + }; + + var updateHash = function(hashCode) { + var groups = ['west', 'central', 'east']; + var hashCodeLetters = hashCode.split(''); + + // Empty hash row + var $hashRow = $('panel.reveal div.hash'); + $hashRow.empty(); + + // Build groups of ruler characters + jQuery.each(groups, function(n, group) { + var $hashGroup = $('').addClass('group ' + group); + + for (var i=0; i < 8; i++) { + var index = n * 8 + i; + var letter = hashCodeLetters[index]; + var $letterSpan = $('').addClass('c').text(letter); + $hashGroup.append($letterSpan); + } + + $hashRow.append($hashGroup); + }); + }; + + var validateInput = function() { + var $warnings = $('panel.reveal div.warnings'); + var site = $('input#locus').val().trim(); + var msw = $('input#pocus').val().trim(); + + // Clear any warning + $warnings.empty(); + + // Add any new warnings + var addWarning = function(message, style) { + style = (! style) ? 'warning' : style; + var alertClass = 'alert alert-dismissible alert-' + style; + var $button = $([ + ''].join('\n')); + var $messageSpan = $('').text(message); + var $alert = $('
') + .addClass(alertClass) + .append($button) + .append($messageSpan); + $warnings.append($alert); + } + + if ( ! site ) { + addWarning('Site field was empty'); + } + + if ( ! msw ) { + addWarning('Mushpup Secret Word field was empty') + } + }; + + // API + return { + init: function() { + prepareRuler(); + prepareMushButtonHandler(); + prepareResetButtonHandler(); + prepareConfirmButtonHandler(); + } + }; +})();